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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +110 -0
- data/ext/ox/base64.c +24 -17
- data/ext/ox/base64.h +2 -1
- data/ext/ox/buf.h +4 -7
- data/ext/ox/builder.c +277 -122
- data/ext/ox/cache.c +48 -13
- data/ext/ox/cache.h +2 -0
- data/ext/ox/dump.c +374 -221
- data/ext/ox/extconf.rb +3 -0
- data/ext/ox/gen_load.c +10 -8
- data/ext/ox/hash_load.c +7 -2
- data/ext/ox/helper.h +10 -6
- data/ext/ox/intern.c +6 -1
- data/ext/ox/obj_load.c +207 -47
- data/ext/ox/ox.c +179 -38
- data/ext/ox/ox.h +11 -1
- data/ext/ox/parse.c +254 -103
- data/ext/ox/sax.c +22 -18
- data/ext/ox/sax_as.c +42 -10
- data/ext/ox/sax_buf.c +23 -8
- data/ext/ox/sax_stack.h +13 -0
- data/ext/ox/time_conv.h +62 -0
- data/ext/ox/xml_check.h +58 -0
- data/ext/ox/xml_str.h +247 -0
- data/lib/ox/version.rb +1 -1
- metadata +5 -2
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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(
|
|
172
|
-
if ('\0' == *pi
|
|
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
|
|
165
|
+
*endp = pi->s;
|
|
177
166
|
break;
|
|
178
167
|
}
|
|
179
|
-
if ('<' != *pi
|
|
180
|
-
set_error(err, "invalid format, expected <", pi
|
|
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
|
|
185
|
-
switch (*pi
|
|
172
|
+
pi->s++; // past <
|
|
173
|
+
switch (*pi->s) {
|
|
186
174
|
case '?': // processing instruction
|
|
187
|
-
pi
|
|
188
|
-
read_instruction(
|
|
175
|
+
pi->s++;
|
|
176
|
+
read_instruction(pi);
|
|
189
177
|
break;
|
|
190
178
|
case '!': // comment or doctype
|
|
191
|
-
pi
|
|
192
|
-
if ('\0' == *pi
|
|
193
|
-
set_error(err, "invalid format, DOCTYPE or comment not terminated", pi
|
|
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
|
|
197
|
-
pi
|
|
198
|
-
if ('-' != *pi
|
|
199
|
-
set_error(err, "invalid format, bad comment format", pi
|
|
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
|
|
204
|
-
read_comment(
|
|
189
|
+
pi->s++; // skip second -
|
|
190
|
+
read_comment(pi);
|
|
205
191
|
}
|
|
206
|
-
} else if ((TolerantEffort == options->effort) ? 0 == strncasecmp("DOCTYPE", pi
|
|
207
|
-
|
|
208
|
-
pi
|
|
209
|
-
read_doctype(
|
|
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
|
|
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(
|
|
203
|
+
read_element(pi, 0);
|
|
222
204
|
body_read = 1;
|
|
223
205
|
break;
|
|
224
206
|
}
|
|
225
|
-
if (err_has(&pi
|
|
226
|
-
*err = pi
|
|
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
|
|
231
|
-
if (NULL != pcb->finish) {
|
|
232
|
-
pcb->finish(
|
|
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
|
|
215
|
+
rb_yield(pi->obj);
|
|
235
216
|
}
|
|
236
217
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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 = π
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
338
|
+
goto CLEANUP;
|
|
299
339
|
}
|
|
300
340
|
next_non_white(pi);
|
|
301
341
|
if (0 == (attr_name = read_name_token(pi))) {
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
745
|
-
char
|
|
746
|
-
char
|
|
747
|
-
char
|
|
748
|
-
char
|
|
749
|
-
|
|
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 . 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
|
-
|
|
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
|
-
|
|
924
|
+
goto CLEANUP;
|
|
783
925
|
}
|
|
926
|
+
/* An entity such as 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
|
-
|
|
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
|
|
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 ||
|
|
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,
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
-
|
|
937
|
+
xfree((char *)ename);
|
|
934
938
|
}
|
|
935
939
|
if ('>' != c) {
|
|
936
940
|
ox_sax_drive_error(dr, WRONG_CHAR "element not closed");
|