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/dump.c CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  #include <errno.h>
7
+ #include <stdbool.h>
7
8
  #include <stdio.h>
8
9
  #include <stdlib.h>
9
10
  #include <string.h>
@@ -12,6 +13,9 @@
12
13
  #include "base64.h"
13
14
  #include "cache8.h"
14
15
  #include "ox.h"
16
+ #include "time_conv.h"
17
+ #include "xml_check.h"
18
+ #include "xml_str.h"
15
19
 
16
20
  #define USE_B64 0
17
21
  #define MAX_DEPTH 1000
@@ -77,18 +81,25 @@ static int is_xml_friendly(const uchar *str, int len, const char *table);
77
81
 
78
82
  static const char hex_chars[17] = "0123456789abcdef";
79
83
 
80
- // The : character is equivalent to 10. Used for replacement characters up to 10
81
- // characters long such as '&#x10FFFF;'.
82
- static const char xml_friendly_chars[257] = "\
83
- :::::::::11::1::::::::::::::::::\
84
- 11611156111111111111111111114141\
85
- 11111111111111111111111111111111\
86
- 11111111111111111111111111111111\
87
- 11111111111111111111111111111111\
88
- 11111111111111111111111111111111\
89
- 11111111111111111111111111111111\
90
- 11111111111111111111111111111111";
84
+ #define APPEND_CHARS(buffer, chars, size) \
85
+ { \
86
+ memcpy(buffer, chars, size); \
87
+ buffer += size; \
88
+ }
89
+
90
+ #ifdef HAVE_FAST_MEMCPY
91
+ #define APPEND_CHARS_SMALL(buffer, chars, size) \
92
+ { \
93
+ fast_memcpy16(buffer, chars, size); \
94
+ buffer += size; \
95
+ }
96
+ #else
97
+ #define APPEND_CHARS_SMALL(buffer, chars, size) APPEND_CHARS(buffer, chars, size)
98
+ #endif
91
99
 
100
+ // Each table below maps a byte to the length of its escaped form, held as an ASCII digit so that
101
+ // xml_str_len() can sum the entries directly. The : character is equivalent to 10, used for replacement
102
+ // characters up to 10 characters long such as '&#x10FFFF;'.
92
103
  static const char xml_quote_chars[257] = "\
93
104
  :::::::::11::1::::::::::::::::::\
94
105
  11611151111111111111111111114141\
@@ -118,15 +129,6 @@ inline static int is_xml_friendly(const uchar *str, int len, const char *table)
118
129
  return 1;
119
130
  }
120
131
 
121
- inline static size_t xml_str_len(const uchar *str, size_t len, const char *table) {
122
- size_t size = 0;
123
-
124
- for (; 0 < len; str++, len--) {
125
- size += xml_friendly_chars[*str];
126
- }
127
- return size - len * (size_t)'0';
128
- }
129
-
130
132
  inline static void dump_hex(uchar c, Out out) {
131
133
  uchar d = (c >> 4) & 0x0F;
132
134
 
@@ -150,9 +152,10 @@ static Type obj_class_code(VALUE obj) {
150
152
  return (is_xml_friendly((uchar *)StringValuePtr(obj), (int)RSTRING_LEN(obj), xml_element_chars)) ? StringCode
151
153
  : String64Code;
152
154
  case T_SYMBOL: {
153
- const char *sym = rb_id2name(SYM2ID(obj));
155
+ volatile VALUE sym = rb_sym2str(obj);
154
156
 
155
- return (is_xml_friendly((uchar *)sym, (int)strlen(sym), xml_element_chars)) ? SymbolCode : Symbol64Code;
157
+ return (is_xml_friendly((uchar *)RSTRING_PTR(sym), (int)RSTRING_LEN(sym), xml_element_chars)) ? SymbolCode
158
+ : Symbol64Code;
156
159
  }
157
160
  case T_DATA: return (rb_cTime == clas) ? TimeCode : ((ox_date_class == clas) ? DateCode : 0);
158
161
  case T_STRUCT: return (rb_cRange == clas) ? RangeCode : StructCode;
@@ -173,49 +176,74 @@ inline static void fill_indent(Out out, int cnt) {
173
176
  memcpy(out->cur, out->opts->margin, out->opts->margin_len);
174
177
  out->cur += out->opts->margin_len;
175
178
  }
176
- for (; 0 < cnt; cnt--) {
177
- *out->cur++ = ' ';
179
+ if (0 < cnt) {
180
+ memset(out->cur, ' ', cnt);
181
+ out->cur += cnt;
178
182
  }
179
183
  }
180
184
  }
181
185
 
182
186
  inline static void fill_value(Out out, const char *value, size_t len) {
183
- if (6 < len) {
184
- memcpy(out->cur, value, len);
185
- out->cur += len;
187
+ if (16 < len) {
188
+ APPEND_CHARS(out->cur, value, len);
186
189
  } else {
187
- for (; 0 < len; len--, value++) {
188
- *out->cur++ = *value;
189
- }
190
+ APPEND_CHARS_SMALL(out->cur, value, len);
190
191
  }
191
192
  }
192
193
 
193
194
  inline static void fill_attr(Out out, char name, const char *value, size_t len) {
194
195
  *out->cur++ = ' ';
195
196
  *out->cur++ = name;
196
- *out->cur++ = '=';
197
- *out->cur++ = '"';
198
- if (6 < len) {
199
- memcpy(out->cur, value, len);
200
- out->cur += len;
197
+ APPEND_CHARS_SMALL(out->cur, "=\"", 2);
198
+ if (16 < len) {
199
+ APPEND_CHARS(out->cur, value, len);
201
200
  } else {
202
- for (; 0 < len; len--, value++) {
203
- *out->cur++ = *value;
204
- }
201
+ APPEND_CHARS_SMALL(out->cur, value, len);
205
202
  }
206
203
  *out->cur++ = '"';
207
204
  }
208
205
 
209
- inline static const char *ulong2str(ulong num, char *end) {
210
- char *b;
206
+ static const char digits_table[] = "\
207
+ 00010203040506070809\
208
+ 10111213141516171819\
209
+ 20212223242526272829\
210
+ 30313233343536373839\
211
+ 40414243444546474849\
212
+ 50515253545556575859\
213
+ 60616263646566676869\
214
+ 70717273747576777879\
215
+ 80818283848586878889\
216
+ 90919293949596979899";
217
+
218
+ static char *ox_longlong_to_string(long long num, bool negative, char *b) {
219
+ while (100 <= num) {
220
+ unsigned idx = (unsigned)(num % 100) * 2;
221
+
222
+ *b-- = digits_table[idx + 1];
223
+ *b-- = digits_table[idx];
224
+ num /= 100;
225
+ }
226
+ if (num < 10) {
227
+ *b-- = (char)(num + '0');
228
+ } else {
229
+ *b-- = digits_table[num * 2 + 1];
230
+ *b-- = digits_table[num * 2];
231
+ }
232
+ if (negative) {
233
+ *b = '-';
234
+ } else {
235
+ b++;
236
+ }
237
+ return b;
238
+ }
211
239
 
240
+ inline static const char *ulong2str(ulong num, char *end) {
212
241
  *end-- = '\0';
213
- for (b = end; 0 < num || b == end; num /= 10, b--) {
214
- *b = (num % 10) + '0';
242
+ if (0 == num) {
243
+ *end = '0';
244
+ return end;
215
245
  }
216
- b++;
217
-
218
- return b;
246
+ return ox_longlong_to_string((long long)num, false, end);
219
247
  }
220
248
 
221
249
  static int check_circular(Out out, VALUE obj, Element e) {
@@ -290,9 +318,7 @@ static void dump_start(Out out, Element e) {
290
318
  }
291
319
  if (e->closed) {
292
320
  if (out->opts->no_empty) {
293
- *out->cur++ = '>';
294
- *out->cur++ = '<';
295
- *out->cur++ = '/';
321
+ APPEND_CHARS_SMALL(out->cur, "></", 3);
296
322
  *out->cur++ = e->type;
297
323
  } else {
298
324
  *out->cur++ = '/';
@@ -309,8 +335,7 @@ static void dump_end(Out out, Element e) {
309
335
  grow(out, size);
310
336
  }
311
337
  fill_indent(out, e->indent);
312
- *out->cur++ = '<';
313
- *out->cur++ = '/';
338
+ APPEND_CHARS_SMALL(out->cur, "</", 2);
314
339
  *out->cur++ = e->type;
315
340
  *out->cur++ = '>';
316
341
  *out->cur = '\0';
@@ -320,13 +345,10 @@ inline static void dump_value(Out out, const char *value, size_t size) {
320
345
  if (out->end - out->cur <= (long)size) {
321
346
  grow(out, size);
322
347
  }
323
- if (6 < size) {
324
- memcpy(out->cur, value, size);
325
- out->cur += size;
348
+ if (16 < size) {
349
+ APPEND_CHARS(out->cur, value, size);
326
350
  } else {
327
- for (; 0 < size; size--, value++) {
328
- *out->cur++ = *value;
329
- }
351
+ APPEND_CHARS_SMALL(out->cur, value, size);
330
352
  }
331
353
  *out->cur = '\0';
332
354
  }
@@ -337,66 +359,88 @@ inline static void dump_str_value(Out out, const char *value, size_t size, const
337
359
  if (out->end - out->cur <= (long)xsize) {
338
360
  grow(out, xsize);
339
361
  }
340
- for (; 0 < size; size--, value++) {
341
- if ('1' == table[(uchar)*value]) {
342
- *out->cur++ = *value;
362
+ if (xsize == size) {
363
+ if (16 < size) {
364
+ APPEND_CHARS(out->cur, value, size);
343
365
  } else {
344
- switch (*value) {
345
- case '"':
346
- *out->cur++ = '&';
347
- *out->cur++ = 'q';
348
- *out->cur++ = 'u';
349
- *out->cur++ = 'o';
350
- *out->cur++ = 't';
351
- *out->cur++ = ';';
352
- break;
353
- case '&':
354
- *out->cur++ = '&';
355
- *out->cur++ = 'a';
356
- *out->cur++ = 'm';
357
- *out->cur++ = 'p';
358
- *out->cur++ = ';';
359
- break;
360
- case '\'':
361
- *out->cur++ = '&';
362
- *out->cur++ = 'a';
363
- *out->cur++ = 'p';
364
- *out->cur++ = 'o';
365
- *out->cur++ = 's';
366
- *out->cur++ = ';';
367
- break;
368
- case '<':
369
- *out->cur++ = '&';
370
- *out->cur++ = 'l';
371
- *out->cur++ = 't';
372
- *out->cur++ = ';';
366
+ APPEND_CHARS_SMALL(out->cur, value, size);
367
+ }
368
+ *out->cur = '\0';
369
+ return;
370
+ }
371
+ const char *end = value + size;
372
+
373
+ while (value < end) {
374
+ /* Copy runs of pass-through bytes a word at a time. A word with no byte
375
+ * of interest is stored whole; on a hit the store is kept up to the
376
+ * first flagged byte and the rest is redone by the loops below. xsize
377
+ * was reserved for the whole escaped result so out->cur stays inside the
378
+ * buffer, and out->cur + 8 <= out->end keeps the wide store in bounds on
379
+ * its own.
380
+ */
381
+ while (value + 8 <= end && out->cur + 8 <= out->end) {
382
+ uint64_t v;
383
+ uint64_t mask;
384
+
385
+ memcpy(&v, value, 8);
386
+ mask = xml_bytes_of_interest(v);
387
+ memcpy(out->cur, value, 8);
388
+ if (0 != mask) {
389
+ int n = xml_first_of_interest((const unsigned char *)value, mask);
390
+
391
+ value += n;
392
+ out->cur += n;
373
393
  break;
374
- case '>':
375
- *out->cur++ = '&';
376
- *out->cur++ = 'g';
377
- *out->cur++ = 't';
378
- *out->cur++ = ';';
394
+ }
395
+ value += 8;
396
+ out->cur += 8;
397
+ }
398
+ /* Pass-through bytes the word loop could not take: the tail shorter than
399
+ * a word, and the '"' or '\'' the element table keeps that the predicate
400
+ * flags anyway.
401
+ */
402
+ while (value < end) {
403
+ uchar c = (uchar)*value;
404
+
405
+ if (c < 0x20 || '"' == c || '\'' == c || '&' == c || '<' == c || '>' == c) {
379
406
  break;
380
- default:
381
- // Must be one of the invalid characters.
382
- if (StrictEffort == out->opts->effort) {
383
- rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", *value);
384
- }
385
- if (Yes == out->opts->allow_invalid) {
386
- *out->cur++ = '&';
387
- *out->cur++ = '#';
388
- *out->cur++ = 'x';
389
- *out->cur++ = '0';
390
- *out->cur++ = '0';
391
- dump_hex(*value, out);
392
- *out->cur++ = ';';
393
- } else if ('\0' != *out->opts->inv_repl) {
394
- // If the empty string then ignore. The first character of
395
- // the replacement is the length.
396
- memcpy(out->cur, out->opts->inv_repl + 1, (size_t)*out->opts->inv_repl);
397
- out->cur += *out->opts->inv_repl;
407
+ }
408
+ *out->cur++ = *value++;
409
+ }
410
+ if (value < end) {
411
+ char c = *value++;
412
+
413
+ if ('1' == table[(uchar)c]) {
414
+ *out->cur++ = c;
415
+ } else {
416
+ switch (c) {
417
+ case '"': APPEND_CHARS_SMALL(out->cur, "&quot;", 6); break;
418
+ case '&': APPEND_CHARS_SMALL(out->cur, "&amp;", 5); break;
419
+ case '\'': APPEND_CHARS_SMALL(out->cur, "&apos;", 6); break;
420
+ case '<': APPEND_CHARS_SMALL(out->cur, "&lt;", 4); break;
421
+ case '>': APPEND_CHARS_SMALL(out->cur, "&gt;", 4); break;
422
+ default:
423
+ // Must be one of the invalid characters.
424
+ if (NotSet == out->opts->allow_invalid) {
425
+ rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", c);
426
+ }
427
+ if (Yes == out->opts->allow_invalid) {
428
+ // A character reference has to resolve to a Char, which #x0 is not.
429
+ // Reading &#x0000; back ends the text at the NUL and drops the rest
430
+ // without an error, so the byte is left out instead.
431
+ if ('\0' != c) {
432
+ APPEND_CHARS_SMALL(out->cur, "&#x00", 5);
433
+ dump_hex(c, out);
434
+ *out->cur++ = ';';
435
+ }
436
+ } else if ('\0' != *out->opts->inv_repl) {
437
+ // If the empty string then ignore. The first character of
438
+ // the replacement is the length.
439
+ memcpy(out->cur, out->opts->inv_repl + 1, (size_t)*out->opts->inv_repl);
440
+ out->cur += *out->opts->inv_repl;
441
+ }
442
+ break;
398
443
  }
399
- break;
400
444
  }
401
445
  }
402
446
  }
@@ -407,30 +451,27 @@ inline static void dump_num(Out out, VALUE obj) {
407
451
  char buf[32];
408
452
  char *b = buf + sizeof(buf) - 1;
409
453
  long num = NUM2LONG(obj);
410
- int neg = 0;
454
+ bool neg = false;
455
+ long size;
411
456
 
412
457
  if (0 > num) {
413
- neg = 1;
458
+ neg = true;
414
459
  num = -num;
415
460
  }
416
461
  *b-- = '\0';
417
462
  if (0 < num) {
418
- for (; 0 < num; num /= 10, b--) {
419
- *b = (num % 10) + '0';
420
- }
421
- if (neg) {
422
- *b = '-';
423
- } else {
424
- b++;
425
- }
463
+ b = ox_longlong_to_string(num, neg, b);
426
464
  } else {
427
465
  *b = '0';
428
466
  }
429
- if (out->end - out->cur <= (long)(sizeof(buf) - (b - buf))) {
430
- grow(out, sizeof(buf) - (b - buf));
467
+ size = sizeof(buf) - (b - buf) - 1;
468
+ if (out->end - out->cur <= size) {
469
+ grow(out, size);
431
470
  }
432
- for (; '\0' != *b; b++) {
433
- *out->cur++ = *b;
471
+ if (16 < size) {
472
+ APPEND_CHARS(out->cur, b, size);
473
+ } else {
474
+ APPEND_CHARS_SMALL(out->cur, b, size);
434
475
  }
435
476
  *out->cur = '\0';
436
477
  }
@@ -443,22 +484,48 @@ static void dump_time_thin(Out out, VALUE obj) {
443
484
  long nsec = ts.tv_nsec;
444
485
  char *dot = b - 10;
445
486
  long size;
487
+ bool neg = 0 > sec;
446
488
 
489
+ // tv_nsec is never negative, so a time before the epoch is a negative
490
+ // second count carrying a positive fraction. Writing those two out as they
491
+ // are would read as -1.5 for what is really -0.5, so move the fraction over
492
+ // and write the magnitude with a sign in front of it.
493
+ if (neg && 0 < nsec) {
494
+ sec++;
495
+ nsec = 1000000000L - nsec;
496
+ }
447
497
  *b-- = '\0';
448
498
  for (; dot < b; b--, nsec /= 10) {
449
499
  *b = '0' + (nsec % 10);
450
500
  }
451
501
  *b-- = '.';
452
- for (; 0 < sec; b--, sec /= 10) {
453
- *b = '0' + (sec % 10);
502
+ if (0 == sec) {
503
+ *b-- = '0';
504
+ } else if (neg) {
505
+ // sec is left negative instead of negated so that the most negative
506
+ // time_t does not overflow. C truncates the quotient toward zero, so
507
+ // the remainder carries the sign with it.
508
+ for (; 0 != sec; b--, sec /= 10) {
509
+ *b = (char)('0' - sec % 10);
510
+ }
511
+ } else {
512
+ for (; 0 < sec; b--, sec /= 10) {
513
+ *b = '0' + (sec % 10);
514
+ }
515
+ }
516
+ if (neg) {
517
+ *b-- = '-';
454
518
  }
455
519
  b++;
456
520
  size = sizeof(buf) - (b - buf) - 1;
457
521
  if (out->end - out->cur <= size) {
458
522
  grow(out, size);
459
523
  }
460
- memcpy(out->cur, b, size);
461
- out->cur += size;
524
+ if (16 < size) {
525
+ APPEND_CHARS(out->cur, b, size);
526
+ } else {
527
+ APPEND_CHARS_SMALL(out->cur, b, size);
528
+ }
462
529
  }
463
530
 
464
531
  static void dump_date(Out out, VALUE obj) {
@@ -468,61 +535,76 @@ static void dump_date(Out out, VALUE obj) {
468
535
  long size;
469
536
 
470
537
  *b-- = '\0';
471
- for (; 0 < jd; b--, jd /= 10) {
472
- *b = '0' + (jd % 10);
473
- }
474
- b++;
475
- if ('\0' == *b) {
476
- b--;
538
+ if (0 < jd) {
539
+ b = ox_longlong_to_string((long long)jd, false, b);
540
+ } else {
477
541
  *b = '0';
478
542
  }
479
543
  size = sizeof(buf) - (b - buf) - 1;
480
544
  if (out->end - out->cur <= size) {
481
545
  grow(out, size);
482
546
  }
483
- memcpy(out->cur, b, size);
484
- out->cur += size;
547
+ if (16 < size) {
548
+ APPEND_CHARS(out->cur, b, size);
549
+ } else {
550
+ APPEND_CHARS_SMALL(out->cur, b, size);
551
+ }
485
552
  }
486
553
 
487
554
  static void dump_time_xsd(Out out, VALUE obj) {
488
- struct tm *tm;
489
- struct timespec ts = rb_time_timespec(obj);
490
- time_t sec = ts.tv_sec;
491
- long nsec = ts.tv_nsec;
555
+ struct timespec ts = rb_time_timespec(obj);
556
+ time_t sec = ts.tv_sec;
557
+ long nsec = ts.tv_nsec;
558
+ long offset = NUM2LONG(rb_funcall2(obj, ox_utc_offset_id, 0, 0));
492
559
  int tzhour, tzmin;
493
560
  char tzsign = '+';
561
+ long long year;
562
+ int mon, day, hour, min, tsec;
563
+ char buf[64];
564
+ int cnt;
494
565
 
495
- if (out->end - out->cur <= 33) {
496
- grow(out, 33);
497
- }
498
566
  /* 2010-07-09T10:47:45.895826+09:00 */
499
- tm = localtime(&sec);
500
- #if HAVE_ST_TM_GMTOFF
501
- if (0 > tm->tm_gmtoff) {
567
+ // The offset comes from the Time rather than from localtime(), which has no
568
+ // tm_gmtoff in the Microsoft CRT and so wrote +00:00 next to a local wall
569
+ // clock there. Adding it before splitting gives that wall clock without a
570
+ // timezone database, so there is no platform branch and no NULL to check.
571
+ // xsd writes the offset as hh:mm, so one that is not a whole number of
572
+ // minutes -- Dublin was -00:25:21 until 1916 -- can not be written without
573
+ // moving the instant. UTC says the same thing and says it exactly.
574
+ if (0 != offset % 60) {
575
+ offset = 0;
576
+ }
577
+ if (0 > offset) {
502
578
  tzsign = '-';
503
- tzhour = (int)(tm->tm_gmtoff / -3600);
504
- tzmin = (int)(tm->tm_gmtoff / -60) - (tzhour * 60);
579
+ tzhour = (int)(offset / -3600);
580
+ tzmin = (int)(offset / -60) - (tzhour * 60);
505
581
  } else {
506
- tzhour = (int)(tm->tm_gmtoff / 3600);
507
- tzmin = (int)(tm->tm_gmtoff / 60) - (tzhour * 60);
582
+ tzhour = (int)(offset / 3600);
583
+ tzmin = (int)(offset / 60) - (tzhour * 60);
508
584
  }
509
- #else
510
- tzhour = 0;
511
- tzmin = 0;
512
- #endif
585
+ ox_civil_from_epoch((int64_t)sec + offset, &year, &mon, &day, &hour, &min, &tsec);
513
586
  /* TBD replace with more efficient printer */
514
- out->cur += sprintf(out->cur,
515
- "%04d-%02d-%02dT%02d:%02d:%02d.%06ld%c%02d:%02d",
516
- tm->tm_year + 1900,
517
- tm->tm_mon + 1,
518
- tm->tm_mday,
519
- tm->tm_hour,
520
- tm->tm_min,
521
- tm->tm_sec,
522
- nsec / 1000,
523
- tzsign,
524
- tzhour,
525
- tzmin);
587
+ // A year outside four digits is longer than the sample above, so reserve
588
+ // what was actually formatted instead of a fixed 33. buf can not overflow:
589
+ // the largest time_t puts the year at 12 digits, and the rest of the format
590
+ // is a fixed 28 characters.
591
+ cnt = snprintf(buf,
592
+ sizeof(buf),
593
+ "%04lld-%02d-%02dT%02d:%02d:%02d.%06ld%c%02d:%02d",
594
+ year,
595
+ mon,
596
+ day,
597
+ hour,
598
+ min,
599
+ tsec,
600
+ nsec / 1000,
601
+ tzsign,
602
+ tzhour,
603
+ tzmin);
604
+ if (out->end - out->cur <= cnt) {
605
+ grow(out, cnt);
606
+ }
607
+ APPEND_CHARS(out->cur, buf, cnt);
526
608
  }
527
609
 
528
610
  static void dump_first_obj(VALUE obj, Out out) {
@@ -711,9 +793,10 @@ static void dump_obj(ID aid, VALUE obj, int depth, Out out) {
711
793
  break;
712
794
  }
713
795
  case T_SYMBOL: {
714
- const char *sym = rb_id2name(SYM2ID(obj));
796
+ volatile VALUE rsym = rb_sym2str(obj);
797
+ const char *sym = RSTRING_PTR(rsym);
715
798
 
716
- cnt = (int)strlen(sym);
799
+ cnt = (int)RSTRING_LEN(rsym);
717
800
  #if USE_B64
718
801
  if (is_xml_friendly((uchar *)sym, cnt)) {
719
802
  e.type = SymbolCode;
@@ -789,18 +872,19 @@ static void dump_obj(ID aid, VALUE obj, int depth, Out out) {
789
872
  }
790
873
  clas = rb_obj_class(obj);
791
874
  if (rb_cRange == clas) {
792
- VALUE beg = RSTRUCT_GET(obj, 0);
793
- VALUE end = RSTRUCT_GET(obj, 1);
794
- VALUE excl = RSTRUCT_GET(obj, 2);
795
- int d2 = depth + 1;
875
+ VALUE beg;
876
+ VALUE end;
877
+ int excl;
878
+ int d2 = depth + 1;
796
879
 
880
+ rb_range_values(obj, &beg, &end, &excl);
797
881
  e.type = RangeCode;
798
882
  e.clas.len = 5;
799
883
  e.clas.str = "Range";
800
884
  out->w_start(out, &e);
801
885
  dump_obj(ox_beg_id, beg, d2, out);
802
886
  dump_obj(ox_end_id, end, d2, out);
803
- dump_obj(ox_excl_id, excl, d2, out);
887
+ dump_obj(ox_excl_id, excl ? Qtrue : Qfalse, d2, out);
804
888
  out->w_end(out, &e);
805
889
  } else {
806
890
  char num_buf[16];
@@ -1009,6 +1093,7 @@ static void dump_gen_element(VALUE obj, int depth, Out out) {
1009
1093
  size_t size;
1010
1094
  int indent;
1011
1095
 
1096
+ check_name_chars(name, (size_t)nlen, false);
1012
1097
  if (0 > out->indent) {
1013
1098
  indent = -1;
1014
1099
  } else if (0 == out->indent) {
@@ -1017,6 +1102,11 @@ static void dump_gen_element(VALUE obj, int depth, Out out) {
1017
1102
  indent = depth * out->indent;
1018
1103
  }
1019
1104
  size = indent + 4 + nlen + out->opts->margin_len;
1105
+ if (0 == depth && 0 < out->indent) {
1106
+ // The margin is written here and again by fill_indent(), so reserve it
1107
+ // twice.
1108
+ size += out->opts->margin_len;
1109
+ }
1020
1110
  if (out->end - out->cur <= (long)size) {
1021
1111
  grow(out, size);
1022
1112
  }
@@ -1030,24 +1120,36 @@ static void dump_gen_element(VALUE obj, int depth, Out out) {
1030
1120
  if (Qnil != attrs) {
1031
1121
  rb_hash_foreach(attrs, dump_gen_attr, (VALUE)out);
1032
1122
  }
1123
+ // The attribute loop runs Ruby, so both the reservation above and the name
1124
+ // pointer are spent: dump_gen_attr() calls rb_String() on each value, and
1125
+ // growing the name String there frees the buffer name points at. The type
1126
+ // check is not repeated: StringValuePtr() above left rname a String and a
1127
+ // VALUE does not change type, only where its bytes live.
1128
+ name = RSTRING_PTR(rname);
1129
+ nlen = RSTRING_LEN(rname);
1130
+ size = indent + 5 + nlen + out->opts->margin_len;
1131
+ if (out->end - out->cur <= (long)size) {
1132
+ grow(out, size);
1133
+ }
1033
1134
  if (Qnil != nodes && 0 < RARRAY_LEN(nodes)) {
1034
1135
  int do_indent;
1035
1136
 
1036
1137
  *out->cur++ = '>';
1037
1138
  do_indent = dump_gen_nodes(nodes, depth, out);
1139
+ // The children run Ruby as well.
1140
+ name = RSTRING_PTR(rname);
1141
+ nlen = RSTRING_LEN(rname);
1142
+ size = indent + 5 + nlen + out->opts->margin_len;
1038
1143
  if (out->end - out->cur <= (long)size) {
1039
1144
  grow(out, size);
1040
1145
  }
1041
1146
  if (do_indent) {
1042
1147
  fill_indent(out, indent);
1043
1148
  }
1044
- *out->cur++ = '<';
1045
- *out->cur++ = '/';
1149
+ APPEND_CHARS_SMALL(out->cur, "</", 2);
1046
1150
  fill_value(out, name, nlen);
1047
1151
  } else if (out->opts->no_empty) {
1048
- *out->cur++ = '>';
1049
- *out->cur++ = '<';
1050
- *out->cur++ = '/';
1152
+ APPEND_CHARS_SMALL(out->cur, "></", 3);
1051
1153
  fill_value(out, name, nlen);
1052
1154
  } else {
1053
1155
  *out->cur++ = '/';
@@ -1066,18 +1168,19 @@ static void dump_gen_instruct(VALUE obj, int depth, Out out) {
1066
1168
  long clen = 0;
1067
1169
  size_t size;
1068
1170
 
1171
+ check_unescaped(name, (size_t)nlen);
1069
1172
  if (T_STRING == rb_type(rcontent)) {
1070
1173
  content = StringValuePtr(rcontent);
1071
1174
  clen = RSTRING_LEN(rcontent);
1072
- size = 4 + nlen + clen;
1175
+ check_unescaped(content, (size_t)clen);
1176
+ size = 4 + nlen + clen;
1073
1177
  } else {
1074
1178
  size = 4 + nlen;
1075
1179
  }
1076
1180
  if (out->end - out->cur <= (long)size) {
1077
1181
  grow(out, size);
1078
1182
  }
1079
- *out->cur++ = '<';
1080
- *out->cur++ = '?';
1183
+ APPEND_CHARS_SMALL(out->cur, "<?", 2);
1081
1184
  fill_value(out, name, nlen);
1082
1185
  if (0 != content) {
1083
1186
  if (' ' != *content) {
@@ -1087,9 +1190,8 @@ static void dump_gen_instruct(VALUE obj, int depth, Out out) {
1087
1190
  } else if (Qnil != attrs) {
1088
1191
  rb_hash_foreach(attrs, dump_gen_attr, (VALUE)out);
1089
1192
  }
1090
- *out->cur++ = '?';
1091
- *out->cur++ = '>';
1092
- *out->cur = '\0';
1193
+ APPEND_CHARS_SMALL(out->cur, "?>", 2);
1194
+ *out->cur = '\0';
1093
1195
  }
1094
1196
 
1095
1197
  static int dump_gen_nodes(VALUE obj, int depth, Out out) {
@@ -1131,31 +1233,34 @@ static int dump_gen_nodes(VALUE obj, int depth, Out out) {
1131
1233
  }
1132
1234
 
1133
1235
  static int dump_gen_attr(VALUE key, VALUE value, VALUE ov) {
1134
- Out out = (Out)ov;
1135
-
1136
- const char *ks;
1137
- size_t klen;
1138
- size_t size;
1236
+ Out out = (Out)ov;
1237
+ volatile VALUE kv = key;
1238
+ const char *ks;
1239
+ size_t klen;
1240
+ size_t size;
1139
1241
 
1140
1242
  switch (rb_type(key)) {
1141
- case T_SYMBOL: ks = rb_id2name(SYM2ID(key)); break;
1142
- case T_STRING: ks = StringValuePtr(key); break;
1143
- default:
1144
- key = rb_String(key);
1145
- ks = StringValuePtr(key);
1146
- break;
1147
- }
1148
- klen = strlen(ks);
1149
- value = rb_String(value);
1150
- size = 4 + klen + RSTRING_LEN(value);
1243
+ case T_SYMBOL: kv = rb_sym2str(key); break;
1244
+ case T_STRING: break;
1245
+ default: kv = rb_String(key); break;
1246
+ }
1247
+ // Every arm above leaves kv a String, so no second type check is needed.
1248
+ ks = RSTRING_PTR(kv);
1249
+ klen = (size_t)RSTRING_LEN(kv);
1250
+ // Before the space, so a rescued raise leaves the element as it was. The
1251
+ // NUL this used to check for on its own is the low end of the same set.
1252
+ check_name_chars(ks, klen, true);
1253
+ if (!RB_TYPE_P(value, T_STRING)) {
1254
+ value = rb_String(value);
1255
+ }
1256
+ size = 4 + klen + RSTRING_LEN(value);
1151
1257
  if (out->end - out->cur <= (long)size) {
1152
1258
  grow(out, size);
1153
1259
  }
1154
1260
  *out->cur++ = ' ';
1155
1261
  fill_value(out, ks, klen);
1156
- *out->cur++ = '=';
1157
- *out->cur++ = '"';
1158
- dump_str_value(out, StringValuePtr(value), RSTRING_LEN(value), xml_quote_chars);
1262
+ APPEND_CHARS_SMALL(out->cur, "=\"", 2);
1263
+ dump_str_value(out, RSTRING_PTR(value), RSTRING_LEN(value), xml_quote_chars);
1159
1264
  *out->cur++ = '"';
1160
1265
 
1161
1266
  return ST_CONTINUE;
@@ -1165,8 +1270,12 @@ static void
1165
1270
  dump_gen_val_node(VALUE obj, int depth, const char *pre, size_t plen, const char *suf, size_t slen, Out out) {
1166
1271
  volatile VALUE v = rb_attr_get(obj, ox_at_value_id);
1167
1272
  const char *val;
1273
+ const char *end;
1274
+ const char *from;
1275
+ const char *split;
1168
1276
  size_t vlen;
1169
1277
  size_t size;
1278
+ size_t cdata_cnt = 0;
1170
1279
  int indent;
1171
1280
 
1172
1281
  if (T_STRING != rb_type(v)) {
@@ -1174,6 +1283,13 @@ dump_gen_val_node(VALUE obj, int depth, const char *pre, size_t plen, const char
1174
1283
  }
1175
1284
  val = StringValuePtr(v);
1176
1285
  vlen = RSTRING_LEN(v);
1286
+ from = val;
1287
+ end = val + vlen;
1288
+ // Ox::Raw is documented as going out untouched, so it is the one value node
1289
+ // that is not checked. The rest are markup ox wrote the delimiters for.
1290
+ if (ox_raw_clas != rb_obj_class(obj)) {
1291
+ check_unescaped(val, vlen);
1292
+ }
1177
1293
  if (0 > out->indent) {
1178
1294
  indent = -1;
1179
1295
  } else if (0 == out->indent) {
@@ -1182,33 +1298,36 @@ dump_gen_val_node(VALUE obj, int depth, const char *pre, size_t plen, const char
1182
1298
  indent = depth * out->indent;
1183
1299
  }
1184
1300
  size = indent + plen + slen + vlen + out->opts->margin_len;
1301
+ // Only a CDATA section can be closed by its own value; see xml_cdata_end().
1302
+ if (9 == plen && 0 == strncmp("<![CDATA[", pre, 9)) {
1303
+ cdata_cnt = xml_cdata_end_cnt(val, end);
1304
+ size += cdata_cnt * CDATA_SPLIT_EXTRA;
1305
+ }
1185
1306
  if (out->end - out->cur <= (long)size) {
1186
1307
  grow(out, size);
1187
1308
  }
1188
1309
  fill_indent(out, indent);
1189
1310
  fill_value(out, pre, plen);
1190
- fill_value(out, val, vlen);
1311
+ // The ']]' stays in this section and the '>' opens the next one, so from
1312
+ // lands on the '>' each time round.
1313
+ while (0 < cdata_cnt && NULL != (split = xml_cdata_end(from, end))) {
1314
+ fill_value(out, from, (size_t)((split + 2) - from));
1315
+ fill_value(out, "]]><![CDATA[", CDATA_SPLIT_EXTRA);
1316
+ from = split + 2;
1317
+ }
1318
+ fill_value(out, from, (size_t)(end - from));
1191
1319
  fill_value(out, suf, slen);
1192
1320
  *out->cur = '\0';
1193
1321
  }
1194
1322
 
1195
- static void dump_obj_to_xml(VALUE obj, Options copts, Out out) {
1323
+ // The dump traversal, run under rb_protect so the output buffer is freed even
1324
+ // when it raises (invalid character, oversized indent, deep-recursion circular
1325
+ // reference, ...). out->obj and out->opts carry everything it needs.
1326
+ static VALUE dump_obj_to_xml_body(VALUE outv) {
1327
+ Out out = (Out)outv;
1328
+ VALUE obj = out->obj;
1196
1329
  VALUE clas = rb_obj_class(obj);
1197
1330
 
1198
- out->w_time = (Yes == copts->xsd_date) ? dump_time_xsd : dump_time_thin;
1199
- out->buf = ALLOC_N(char, 65336);
1200
- out->end = out->buf + 65325; /* 10 less than end plus extra for possible errors */
1201
- out->cur = out->buf;
1202
- out->circ_cache = 0;
1203
- out->circ_cnt = 0;
1204
- out->opts = copts;
1205
- out->obj = obj;
1206
- *out->cur = '\0';
1207
- if (Yes == copts->circular) {
1208
- ox_cache8_new(&out->circ_cache);
1209
- }
1210
- out->indent = copts->indent;
1211
-
1212
1331
  if (ox_document_clas == clas) {
1213
1332
  dump_gen_doc(obj, -1, out);
1214
1333
  } else if (ox_element_clas == clas) {
@@ -1229,9 +1348,39 @@ static void dump_obj_to_xml(VALUE obj, Options copts, Out out) {
1229
1348
  if (0 <= out->indent) {
1230
1349
  dump_value(out, "\n", 1);
1231
1350
  }
1351
+ return Qnil;
1352
+ }
1353
+
1354
+ static void dump_obj_to_xml(VALUE obj, Options copts, Out out) {
1355
+ int state = 0;
1356
+
1357
+ out->w_time = (Yes == copts->xsd_date) ? dump_time_xsd : dump_time_thin;
1358
+ out->buf = ALLOC_N(char, 65336);
1359
+ out->end = out->buf + 65325; /* 10 less than end plus extra for possible errors */
1360
+ out->cur = out->buf;
1361
+ out->circ_cache = 0;
1362
+ out->circ_cnt = 0;
1363
+ out->opts = copts;
1364
+ out->obj = obj;
1365
+ *out->cur = '\0';
1366
+ if (Yes == copts->circular) {
1367
+ ox_cache8_new(&out->circ_cache);
1368
+ }
1369
+ out->indent = copts->indent;
1370
+
1371
+ // Run the traversal under rb_protect. On the normal path out->buf is handed
1372
+ // back to the caller; on a raise the longjmp would otherwise skip the free,
1373
+ // so release the buffer (and the circular cache) here before re-raising.
1374
+ rb_protect(dump_obj_to_xml_body, (VALUE)out, &state);
1375
+
1232
1376
  if (Yes == copts->circular) {
1233
1377
  ox_cache8_delete(out->circ_cache);
1234
1378
  }
1379
+ if (0 != state) {
1380
+ xfree(out->buf);
1381
+ out->buf = NULL;
1382
+ rb_jump_tag(state);
1383
+ }
1235
1384
  }
1236
1385
 
1237
1386
  char *ox_write_obj_to_str(VALUE obj, Options copts) {
@@ -1248,11 +1397,15 @@ void ox_write_obj_to_file(VALUE obj, const char *path, Options copts) {
1248
1397
 
1249
1398
  dump_obj_to_xml(obj, copts, &out);
1250
1399
  size = out.cur - out.buf;
1251
- if (0 == (f = fopen(path, "w"))) {
1400
+ if (0 == (f = fopen(path, "wb"))) {
1401
+ xfree(out.buf);
1252
1402
  rb_raise(rb_eIOError, "%s\n", strerror(errno));
1253
1403
  }
1254
1404
  if (size != fwrite(out.buf, 1, size, f)) {
1255
1405
  int err = ferror(f);
1406
+
1407
+ fclose(f);
1408
+ xfree(out.buf);
1256
1409
  rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err));
1257
1410
  }
1258
1411
  xfree(out.buf);