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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +103 -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 +249 -106
- 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>
|
|
@@ -19,9 +20,6 @@
|
|
|
19
20
|
#include "special.h"
|
|
20
21
|
|
|
21
22
|
#define MAX_ELEMENT_DEPTH 1000
|
|
22
|
-
// Anything even close to the max prolog length is most likely someone trying
|
|
23
|
-
// to break something.
|
|
24
|
-
#define MAX_PROLOG 32767
|
|
25
23
|
|
|
26
24
|
static void mark_pi_cb(void *ptr);
|
|
27
25
|
static void read_instruction(PInfo pi);
|
|
@@ -140,115 +138,155 @@ static void mark_pi_cb(void *ptr) {
|
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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;
|
|
149
158
|
|
|
150
|
-
if (0 == xml) {
|
|
151
|
-
set_error(err, "Invalid arg, xml string can not be null", xml, 0);
|
|
152
|
-
return Qnil;
|
|
153
|
-
}
|
|
154
|
-
if (DEBUG <= options->trace) {
|
|
155
|
-
printf("Parsing xml:\n%s\n", xml);
|
|
156
|
-
}
|
|
157
|
-
// initialize parse info
|
|
158
|
-
helper_stack_init(&pi.helpers);
|
|
159
|
-
// Protect against GC
|
|
160
|
-
wrap = TypedData_Wrap_Struct(rb_cObject, &ox_wrap_type, &pi);
|
|
161
|
-
|
|
162
|
-
err_init(&pi.err);
|
|
163
|
-
pi.str = xml;
|
|
164
|
-
pi.end = pi.str + len;
|
|
165
|
-
pi.s = xml;
|
|
166
|
-
pi.pcb = pcb;
|
|
167
|
-
pi.obj = Qnil;
|
|
168
|
-
pi.circ_array = 0;
|
|
169
|
-
pi.options = options;
|
|
170
|
-
pi.marked = NULL;
|
|
171
|
-
pi.mark_size = 0;
|
|
172
|
-
pi.mark_cnt = 0;
|
|
173
159
|
while (1) {
|
|
174
|
-
next_non_white(
|
|
175
|
-
if ('\0' == *pi
|
|
160
|
+
next_non_white(pi); // skip white space
|
|
161
|
+
if ('\0' == *pi->s) {
|
|
176
162
|
break;
|
|
177
163
|
}
|
|
178
164
|
if (body_read && 0 != endp) {
|
|
179
|
-
*endp = pi
|
|
165
|
+
*endp = pi->s;
|
|
180
166
|
break;
|
|
181
167
|
}
|
|
182
|
-
if ('<' != *pi
|
|
183
|
-
set_error(err, "invalid format, expected <", pi
|
|
184
|
-
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);
|
|
185
170
|
return Qnil;
|
|
186
171
|
}
|
|
187
|
-
pi
|
|
188
|
-
switch (*pi
|
|
172
|
+
pi->s++; // past <
|
|
173
|
+
switch (*pi->s) {
|
|
189
174
|
case '?': // processing instruction
|
|
190
|
-
pi
|
|
191
|
-
read_instruction(
|
|
175
|
+
pi->s++;
|
|
176
|
+
read_instruction(pi);
|
|
192
177
|
break;
|
|
193
178
|
case '!': // comment or doctype
|
|
194
|
-
pi
|
|
195
|
-
if ('\0' == *pi
|
|
196
|
-
set_error(err, "invalid format, DOCTYPE or comment not terminated", pi
|
|
197
|
-
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);
|
|
198
182
|
return Qnil;
|
|
199
|
-
} else if ('-' == *pi
|
|
200
|
-
pi
|
|
201
|
-
if ('-' != *pi
|
|
202
|
-
set_error(err, "invalid format, bad comment format", pi
|
|
203
|
-
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);
|
|
204
187
|
return Qnil;
|
|
205
188
|
} else {
|
|
206
|
-
pi
|
|
207
|
-
read_comment(
|
|
189
|
+
pi->s++; // skip second -
|
|
190
|
+
read_comment(pi);
|
|
208
191
|
}
|
|
209
|
-
} else if ((TolerantEffort == options->effort) ? 0 == strncasecmp("DOCTYPE", pi
|
|
210
|
-
|
|
211
|
-
pi
|
|
212
|
-
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);
|
|
213
196
|
} else {
|
|
214
|
-
set_error(err, "invalid format, DOCTYPE or comment expected", pi
|
|
215
|
-
helper_stack_cleanup(&pi.helpers);
|
|
197
|
+
set_error(err, "invalid format, DOCTYPE or comment expected", pi->str, pi->s);
|
|
216
198
|
return Qnil;
|
|
217
199
|
}
|
|
218
200
|
break;
|
|
219
|
-
case '\0':
|
|
220
|
-
set_error(err, "invalid format, document not terminated", pi.str, pi.s);
|
|
221
|
-
helper_stack_cleanup(&pi.helpers);
|
|
222
|
-
return Qnil;
|
|
201
|
+
case '\0': set_error(err, "invalid format, document not terminated", pi->str, pi->s); return Qnil;
|
|
223
202
|
default:
|
|
224
|
-
read_element(
|
|
203
|
+
read_element(pi, 0);
|
|
225
204
|
body_read = 1;
|
|
226
205
|
break;
|
|
227
206
|
}
|
|
228
|
-
if (err_has(&pi
|
|
229
|
-
*err = pi
|
|
230
|
-
helper_stack_cleanup(&pi.helpers);
|
|
207
|
+
if (err_has(&pi->err)) {
|
|
208
|
+
*err = pi->err;
|
|
231
209
|
return Qnil;
|
|
232
210
|
}
|
|
233
|
-
if (block_given && Qnil != pi
|
|
234
|
-
if (NULL != pcb->finish) {
|
|
235
|
-
pcb->finish(
|
|
211
|
+
if (block_given && Qnil != pi->obj && Qundef != pi->obj) {
|
|
212
|
+
if (NULL != pi->pcb->finish) {
|
|
213
|
+
pi->pcb->finish(pi);
|
|
236
214
|
}
|
|
237
|
-
rb_yield(pi
|
|
215
|
+
rb_yield(pi->obj);
|
|
238
216
|
}
|
|
239
217
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (NULL != pcb->finish) {
|
|
243
|
-
pcb->finish(&pi);
|
|
218
|
+
if (NULL != pi->pcb->finish) {
|
|
219
|
+
pi->pcb->finish(pi);
|
|
244
220
|
}
|
|
245
|
-
return pi
|
|
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);
|
|
260
|
+
}
|
|
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);
|
|
246
284
|
}
|
|
247
285
|
|
|
248
286
|
// Entered after the "<?" sequence. Ready to read the rest.
|
|
249
287
|
static void read_instruction(PInfo pi) {
|
|
250
288
|
char content[256];
|
|
251
|
-
char *content_ptr;
|
|
289
|
+
char *content_ptr = content;
|
|
252
290
|
struct _attrStack attrs;
|
|
253
291
|
char *attr_name;
|
|
254
292
|
char *attr_value;
|
|
@@ -262,7 +300,7 @@ static void read_instruction(PInfo pi) {
|
|
|
262
300
|
*content = '\0';
|
|
263
301
|
attr_stack_init(&attrs);
|
|
264
302
|
if (0 == (target = read_name_token(pi))) {
|
|
265
|
-
|
|
303
|
+
goto CLEANUP;
|
|
266
304
|
}
|
|
267
305
|
end = pi->s;
|
|
268
306
|
for (; true; pi->s++) {
|
|
@@ -273,7 +311,7 @@ static void read_instruction(PInfo pi) {
|
|
|
273
311
|
goto DONE;
|
|
274
312
|
}
|
|
275
313
|
break;
|
|
276
|
-
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;
|
|
277
315
|
default: break;
|
|
278
316
|
}
|
|
279
317
|
}
|
|
@@ -296,21 +334,18 @@ DONE:
|
|
|
296
334
|
while ('?' != c) {
|
|
297
335
|
pi->last = 0;
|
|
298
336
|
if ('\0' == *pi->s) {
|
|
299
|
-
attr_stack_cleanup(&attrs);
|
|
300
337
|
set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
|
|
301
|
-
|
|
338
|
+
goto CLEANUP;
|
|
302
339
|
}
|
|
303
340
|
next_non_white(pi);
|
|
304
341
|
if (0 == (attr_name = read_name_token(pi))) {
|
|
305
|
-
|
|
306
|
-
return;
|
|
342
|
+
goto CLEANUP;
|
|
307
343
|
}
|
|
308
344
|
end = pi->s;
|
|
309
345
|
next_non_white(pi);
|
|
310
346
|
if ('\0' == *pi->s) {
|
|
311
|
-
attr_stack_cleanup(&attrs);
|
|
312
347
|
set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
|
|
313
|
-
|
|
348
|
+
goto CLEANUP;
|
|
314
349
|
}
|
|
315
350
|
if ('=' != *pi->s++) {
|
|
316
351
|
attrs_ok = false;
|
|
@@ -320,8 +355,7 @@ DONE:
|
|
|
320
355
|
// read value
|
|
321
356
|
next_non_white(pi);
|
|
322
357
|
if (0 == (attr_value = read_quoted_value(pi))) {
|
|
323
|
-
|
|
324
|
-
return;
|
|
358
|
+
goto CLEANUP;
|
|
325
359
|
}
|
|
326
360
|
attr_stack_push(&attrs, attr_name, attr_value);
|
|
327
361
|
next_non_white(pi);
|
|
@@ -339,9 +373,8 @@ DONE:
|
|
|
339
373
|
}
|
|
340
374
|
if (attrs_ok) {
|
|
341
375
|
if ('>' != *pi->s++) {
|
|
342
|
-
attr_stack_cleanup(&attrs);
|
|
343
376
|
set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
|
|
344
|
-
|
|
377
|
+
goto CLEANUP;
|
|
345
378
|
}
|
|
346
379
|
} else {
|
|
347
380
|
pi->s = cend + 1;
|
|
@@ -362,6 +395,9 @@ DONE:
|
|
|
362
395
|
}
|
|
363
396
|
}
|
|
364
397
|
}
|
|
398
|
+
// Single exit, so no error return can leak the heap content buffer or the
|
|
399
|
+
// heap-grown attribute stack.
|
|
400
|
+
CLEANUP:
|
|
365
401
|
attr_stack_cleanup(&attrs);
|
|
366
402
|
if (content_ptr != content) {
|
|
367
403
|
xfree(content_ptr);
|
|
@@ -567,6 +603,7 @@ static char *read_element(PInfo pi, int depth) {
|
|
|
567
603
|
/* read value */
|
|
568
604
|
next_non_white(pi);
|
|
569
605
|
if (0 == (attr_value = read_quoted_value(pi))) {
|
|
606
|
+
attr_stack_cleanup(&attrs);
|
|
570
607
|
return 0;
|
|
571
608
|
}
|
|
572
609
|
if (pi->options->convert_special && 0 != strchr(attr_value, '&')) {
|
|
@@ -597,7 +634,7 @@ static char *read_element(PInfo pi, int depth) {
|
|
|
597
634
|
if (OffSkip == pi->options->skip && start < pi->s && '<' == *pi->s) {
|
|
598
635
|
c = *pi->s;
|
|
599
636
|
*pi->s = '\0';
|
|
600
|
-
pi->pcb->add_text(pi, start, 1);
|
|
637
|
+
pi->pcb->add_text(pi, start, (size_t)(pi->s - start), 1);
|
|
601
638
|
*pi->s = c;
|
|
602
639
|
}
|
|
603
640
|
c = *pi->s++;
|
|
@@ -679,7 +716,9 @@ static char *read_element(PInfo pi, int depth) {
|
|
|
679
716
|
default: break;
|
|
680
717
|
}
|
|
681
718
|
if ('\0' != *start) {
|
|
682
|
-
|
|
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);
|
|
683
722
|
}
|
|
684
723
|
}
|
|
685
724
|
pi->s++;
|
|
@@ -711,6 +750,7 @@ static char *read_element(PInfo pi, int depth) {
|
|
|
711
750
|
return name;
|
|
712
751
|
}
|
|
713
752
|
} else if (err_has(&pi->err)) {
|
|
753
|
+
attr_stack_cleanup(&attrs);
|
|
714
754
|
return 0;
|
|
715
755
|
}
|
|
716
756
|
break;
|
|
@@ -748,15 +788,109 @@ static char *read_element(PInfo pi, int depth) {
|
|
|
748
788
|
return 0;
|
|
749
789
|
}
|
|
750
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
|
+
|
|
751
838
|
static void read_text(PInfo pi) {
|
|
752
|
-
char
|
|
753
|
-
char
|
|
754
|
-
char
|
|
755
|
-
char
|
|
756
|
-
char
|
|
757
|
-
|
|
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);
|
|
758
854
|
|
|
759
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
|
+
}
|
|
760
894
|
c = *pi->s++;
|
|
761
895
|
switch (c) {
|
|
762
896
|
case '<':
|
|
@@ -766,7 +900,7 @@ static void read_text(PInfo pi) {
|
|
|
766
900
|
case '\0':
|
|
767
901
|
pi->s--;
|
|
768
902
|
set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s);
|
|
769
|
-
|
|
903
|
+
goto CLEANUP;
|
|
770
904
|
default:
|
|
771
905
|
if (end <= (b + (('&' == c) ? 7 : 0))) { /* extra 8 for special just in case it is sequence of bytes */
|
|
772
906
|
unsigned long size;
|
|
@@ -787,13 +921,15 @@ static void read_text(PInfo pi) {
|
|
|
787
921
|
}
|
|
788
922
|
if ('&' == c) {
|
|
789
923
|
if (0 == (b = read_coded_chars(pi, b))) {
|
|
790
|
-
|
|
924
|
+
goto CLEANUP;
|
|
791
925
|
}
|
|
926
|
+
/* An entity such as can decode to '\r'; be conservative. */
|
|
927
|
+
maybe_cr = 1;
|
|
792
928
|
} else {
|
|
793
929
|
if (0 <= c && c <= 0x20) {
|
|
794
930
|
if (StrictEffort == pi->options->effort && 'x' == xml_valid_lower_chars[(unsigned char)c]) {
|
|
795
931
|
set_error(&pi->err, "invalid character", pi->str, pi->s);
|
|
796
|
-
|
|
932
|
+
goto CLEANUP;
|
|
797
933
|
}
|
|
798
934
|
switch (pi->options->skip) {
|
|
799
935
|
case CrSkip:
|
|
@@ -823,14 +959,21 @@ static void read_text(PInfo pi) {
|
|
|
823
959
|
break;
|
|
824
960
|
}
|
|
825
961
|
}
|
|
826
|
-
*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:
|
|
827
975
|
if (0 != alloc_buf) {
|
|
828
|
-
fix_newlines(alloc_buf);
|
|
829
|
-
pi->pcb->add_text(pi, alloc_buf, ('/' == *(pi->s + 1)));
|
|
830
976
|
xfree(alloc_buf);
|
|
831
|
-
} else {
|
|
832
|
-
fix_newlines(buf);
|
|
833
|
-
pi->pcb->add_text(pi, buf, ('/' == *(pi->s + 1)));
|
|
834
977
|
}
|
|
835
978
|
}
|
|
836
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");
|