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/builder.c CHANGED
@@ -14,6 +14,8 @@
14
14
  #include "ruby.h"
15
15
  #include "ruby/encoding.h"
16
16
  #include "ruby/version.h"
17
+ #include "xml_check.h"
18
+ #include "xml_str.h"
17
19
 
18
20
  #define MAX_DEPTH 128
19
21
 
@@ -92,20 +94,13 @@ static const char xml_element_chars[257] = "\
92
94
  11111111111111111111111111111111\
93
95
  11111111111111111111111111111111";
94
96
 
95
- inline static size_t xml_str_len(const unsigned char *str, size_t len, const char *table) {
96
- size_t size = 0;
97
-
98
- for (; 0 < len; str++, len--) {
99
- size += table[*str];
100
- }
101
- return size - len * (size_t)'0';
102
- }
103
-
104
97
  static void append_indent(Builder b) {
105
98
  if (0 >= b->indent) {
106
99
  return;
107
100
  }
108
- if (b->buf.head < b->buf.tail) {
101
+ // pos and not the buffer: a file builder empties the buffer on every flush,
102
+ // which would read as the start of the document again.
103
+ if (0 < b->pos) {
109
104
  int cnt = (b->indent * (b->depth + 1)) + 1;
110
105
 
111
106
  if (sizeof(indent_spaces) <= (size_t)cnt) {
@@ -118,9 +113,62 @@ static void append_indent(Builder b) {
118
113
  }
119
114
  }
120
115
 
121
- static void append_string(Builder b, const char *str, size_t size, const char *table, bool strip_invalid_chars) {
116
+ // Returns the escaped size append_string() needs, and raises first if the value
117
+ // holds a character XML has no way to write. append_string() raises on the same
118
+ // byte, but only once the delimiters around the value are already in the buffer
119
+ // and, for an element, once the element is on the stack, and nothing takes those
120
+ // back. So the writers ask here before they write anything.
121
+ //
122
+ // Only a value whose escaped form is longer can hold one, since an invalid byte
123
+ // counts 10, so the common case is the one walk append_string() always did.
124
+ inline static size_t check_string(const char *str, size_t size, const char *table) {
122
125
  size_t xsize = xml_str_len((const unsigned char *)str, size, table);
123
126
 
127
+ if (xsize != size) {
128
+ const unsigned char *bad = xml_first_invalid((const unsigned char *)str, size);
129
+
130
+ if (NULL != bad) {
131
+ rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", *bad);
132
+ }
133
+ }
134
+ return xsize;
135
+ }
136
+
137
+ // One of the instruct() attributes, which are written from the Hash rather than
138
+ // passed in. A value of the wrong type is left to the type check that already
139
+ // refuses it further down.
140
+ inline static void check_instruct_value(VALUE attrs, VALUE key) {
141
+ volatile VALUE v = rb_hash_lookup(attrs, key);
142
+
143
+ if (Qnil != v && rb_cString == rb_obj_class(v)) {
144
+ check_unescaped(StringValuePtr(v), (size_t)RSTRING_LEN(v));
145
+ }
146
+ }
147
+
148
+ // Resolves a Symbol or String name to bytes and checks it, for the writers that
149
+ // take either. sym holds the Symbol's name String for as long as *strp is used.
150
+ inline static long check_name(VALUE v, volatile VALUE *sym, const char **strp, size_t *xsizep, const char *msg) {
151
+ long len;
152
+
153
+ switch (rb_type(v)) {
154
+ case T_STRING:
155
+ *strp = StringValuePtr(v);
156
+ len = RSTRING_LEN(v);
157
+ break;
158
+ case T_SYMBOL:
159
+ *sym = rb_sym2str(v);
160
+ *strp = RSTRING_PTR(*sym);
161
+ len = RSTRING_LEN(*sym);
162
+ break;
163
+ default: rb_raise(ox_arg_error_class, "%s", msg); break;
164
+ }
165
+ *xsizep = check_string(*strp, (size_t)len, xml_element_chars);
166
+
167
+ return len;
168
+ }
169
+
170
+ static void
171
+ append_string(Builder b, const char *str, size_t size, const char *table, size_t xsize, bool strip_invalid_chars) {
124
172
  if (size == xsize) {
125
173
  const char *s = str;
126
174
  const char *end = str + size;
@@ -135,45 +183,107 @@ static void append_string(Builder b, const char *str, size_t size, const char *t
135
183
  }
136
184
  b->pos += size;
137
185
  } else {
138
- char buf[256];
139
- char *end = buf + sizeof(buf) - 1;
140
- char *bp = buf;
141
- size_t i = size;
142
- int fcnt;
143
-
144
- for (; '\0' != *str && 0 < i; i--, str++) {
145
- if ('1' == (fcnt = table[(unsigned char)*str])) {
146
- if (end <= bp) {
186
+ char buf[256];
187
+ char *bp = buf;
188
+ char *bend = buf + sizeof(buf) - 1;
189
+ const char *send = str + size;
190
+
191
+ while (str < send) {
192
+ /* Copy runs of pass-through bytes a word at a time into the staging
193
+ * buffer. A clean word has no byte below 0x20, so it can hold no
194
+ * newline and no '\0': col and pos advance by the whole word and the
195
+ * line is unchanged. On a hit the store is kept up to the first
196
+ * flagged byte and the rest is redone by the loops below.
197
+ */
198
+ while (str + 8 <= send) {
199
+ uint64_t v;
200
+ uint64_t mask;
201
+
202
+ memcpy(&v, str, 8);
203
+ mask = xml_bytes_of_interest(v);
204
+ if (bend < bp + 8) {
147
205
  buf_append_string(&b->buf, buf, bp - buf);
148
206
  bp = buf;
149
207
  }
150
- if ('\n' == *str) {
151
- b->line++;
152
- b->col = 1;
153
- } else {
154
- b->col++;
208
+ memcpy(bp, str, 8);
209
+ if (0 != mask) {
210
+ int n = xml_first_of_interest((const unsigned char *)str, mask);
211
+
212
+ bp += n;
213
+ str += n;
214
+ b->col += n;
215
+ b->pos += n;
216
+ break;
155
217
  }
156
- b->pos++;
157
- *bp++ = *str;
158
- } else {
159
- b->pos += fcnt - '0';
160
- b->col += fcnt - '0';
161
- if (buf < bp) {
218
+ bp += 8;
219
+ str += 8;
220
+ b->col += 8;
221
+ b->pos += 8;
222
+ }
223
+ /* Pass-through bytes the word loop left: the tail shorter than a
224
+ * word. These are all >= 0x20 and not escape characters, so no
225
+ * newline check is needed.
226
+ */
227
+ while (str < send) {
228
+ unsigned char c = (unsigned char)*str;
229
+
230
+ if (c < 0x20 || '"' == c || '\'' == c || '&' == c || '<' == c || '>' == c) {
231
+ break;
232
+ }
233
+ if (bend <= bp) {
162
234
  buf_append_string(&b->buf, buf, bp - buf);
163
235
  bp = buf;
164
236
  }
165
- switch (*str) {
166
- case '"': buf_append_string(&b->buf, "&quot;", 6); break;
167
- case '&': buf_append_string(&b->buf, "&amp;", 5); break;
168
- case '\'': buf_append_string(&b->buf, "&apos;", 6); break;
169
- case '<': buf_append_string(&b->buf, "&lt;", 4); break;
170
- case '>': buf_append_string(&b->buf, "&gt;", 4); break;
171
- default:
172
- // Must be one of the invalid characters.
173
- if (!strip_invalid_chars) {
174
- rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", *str);
237
+ *bp++ = *str++;
238
+ b->col++;
239
+ b->pos++;
240
+ }
241
+ /* One byte the predicate flagged: an escape, an invalid character,
242
+ * or a byte the table keeps unchanged ('"' and '\'' in the element
243
+ * table, and 0x09/0x0a/0x0d in every table).
244
+ */
245
+ if (str < send) {
246
+ int fcnt = table[(unsigned char)*str];
247
+
248
+ if ('1' == fcnt) {
249
+ if (bend <= bp) {
250
+ buf_append_string(&b->buf, buf, bp - buf);
251
+ bp = buf;
175
252
  }
176
- break;
253
+ if ('\n' == *str) {
254
+ b->line++;
255
+ b->col = 1;
256
+ } else {
257
+ b->col++;
258
+ }
259
+ b->pos++;
260
+ *bp++ = *str++;
261
+ } else {
262
+ size_t written = (size_t)(fcnt - '0');
263
+
264
+ if (buf < bp) {
265
+ buf_append_string(&b->buf, buf, bp - buf);
266
+ bp = buf;
267
+ }
268
+ switch (*str) {
269
+ case '"': buf_append_string(&b->buf, "&quot;", 6); break;
270
+ case '&': buf_append_string(&b->buf, "&amp;", 5); break;
271
+ case '\'': buf_append_string(&b->buf, "&apos;", 6); break;
272
+ case '<': buf_append_string(&b->buf, "&lt;", 4); break;
273
+ case '>': buf_append_string(&b->buf, "&gt;", 4); break;
274
+ default:
275
+ // Must be one of the invalid characters. The table holds
276
+ // 10 for those, the longest escape, but nothing is
277
+ // written for them here.
278
+ if (!strip_invalid_chars) {
279
+ rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", *str);
280
+ }
281
+ written = 0;
282
+ break;
283
+ }
284
+ b->pos += written;
285
+ b->col += written;
286
+ str++;
177
287
  }
178
288
  }
179
289
  }
@@ -185,21 +295,12 @@ static void append_string(Builder b, const char *str, size_t size, const char *t
185
295
  }
186
296
 
187
297
  static void append_sym_str(Builder b, VALUE v) {
188
- const char *s;
189
- long len;
298
+ volatile VALUE sym = Qnil;
299
+ const char *s;
300
+ size_t xsize = 0;
301
+ long len = check_name(v, &sym, &s, &xsize, "expected a Symbol or String");
190
302
 
191
- switch (rb_type(v)) {
192
- case T_STRING:
193
- s = StringValuePtr(v);
194
- len = RSTRING_LEN(v);
195
- break;
196
- case T_SYMBOL:
197
- s = rb_id2name(SYM2ID(v));
198
- len = strlen(s);
199
- break;
200
- default: rb_raise(ox_arg_error_class, "expected a Symbol or String"); break;
201
- }
202
- append_string(b, s, len, xml_element_chars, false);
303
+ append_string(b, s, len, xml_element_chars, xsize, false);
203
304
  }
204
305
 
205
306
  static void i_am_a_child(Builder b, bool is_text) {
@@ -219,17 +320,30 @@ static void i_am_a_child(Builder b, bool is_text) {
219
320
  }
220
321
 
221
322
  static int append_attr(VALUE key, VALUE value, VALUE bv) {
222
- Builder b = (Builder)bv;
323
+ Builder b = (Builder)bv;
324
+ volatile VALUE sym = Qnil;
325
+ const char *ks;
326
+ long klen;
327
+ size_t kx;
328
+ size_t vsize;
329
+ size_t vx;
330
+
331
+ // Both halves before the space: an attribute is either written whole or not
332
+ // at all, and the element it belongs to is left as it was.
333
+ Check_Type(value, T_STRING);
334
+ klen = check_name(key, &sym, &ks, &kx, "expected a Symbol or String");
335
+ check_name_chars(ks, (size_t)klen, true);
336
+ vsize = (size_t)RSTRING_LEN(value);
337
+ vx = check_string(StringValuePtr(value), vsize, xml_attr_chars);
223
338
 
224
339
  buf_append(&b->buf, ' ');
225
340
  b->col++;
226
341
  b->pos++;
227
- append_sym_str(b, key);
342
+ append_string(b, ks, klen, xml_element_chars, kx, false);
228
343
  buf_append_string(&b->buf, "=\"", 2);
229
344
  b->col += 2;
230
345
  b->pos += 2;
231
- Check_Type(value, T_STRING);
232
- append_string(b, StringValuePtr(value), (int)RSTRING_LEN(value), xml_attr_chars, false);
346
+ append_string(b, StringValuePtr(value), vsize, xml_attr_chars, vx, false);
233
347
  buf_append(&b->buf, '"');
234
348
  b->col++;
235
349
  b->pos++;
@@ -257,7 +371,7 @@ static void builder_free(void *ptr) {
257
371
  }
258
372
  b = (Builder)ptr;
259
373
  buf_cleanup(&b->buf);
260
- for (e = b->stack, d = b->depth; 0 < d; d--, e++) {
374
+ for (e = b->stack, d = b->depth; 0 <= d; d--, e++) {
261
375
  if (e->name != e->buf) {
262
376
  free(e->name);
263
377
  }
@@ -278,10 +392,17 @@ static void pop(Builder b) {
278
392
  append_indent(b);
279
393
  }
280
394
  buf_append_string(&b->buf, "</", 2);
281
- append_string(b, e->name, e->len, xml_element_chars, false);
395
+ append_string(b,
396
+ e->name,
397
+ e->len,
398
+ xml_element_chars,
399
+ xml_str_len((const unsigned char *)e->name, e->len, xml_element_chars),
400
+ false);
282
401
  buf_append(&b->buf, '>');
283
- b->col += e->len + 3;
284
- b->pos += e->len + 3;
402
+ // append_string() already counted the name, so this is only the "</"
403
+ // and the ">".
404
+ b->col += 3;
405
+ b->pos += 3;
285
406
  if (e->buf != e->name) {
286
407
  free(e->name);
287
408
  e->name = 0;
@@ -311,18 +432,21 @@ static void bclose(Builder b) {
311
432
 
312
433
  static VALUE to_s(Builder b) {
313
434
  volatile VALUE rstr;
435
+ size_t len;
314
436
 
315
437
  if (0 != b->buf.fd) {
316
438
  rb_raise(ox_arg_error_class, "can not create a String with a stream or file builder.");
317
439
  }
318
- if (0 <= b->indent && '\n' != *(b->buf.tail - 1)) {
319
- buf_append(&b->buf, '\n');
320
- b->line++;
321
- b->col = 1;
322
- b->pos++;
323
- }
440
+ len = buf_len(&b->buf);
324
441
  *b->buf.tail = '\0'; // for debugging
325
- rstr = rb_str_new(b->buf.head, buf_len(&b->buf));
442
+ rstr = rb_str_new(b->buf.head, len);
443
+ // The closing newline goes on the String and not the buffer. Appended to the
444
+ // buffer it stays there, so a to_s taken before the document is finished
445
+ // leaves a newline in the middle of it, and in the middle of a value if that
446
+ // is where the builder was.
447
+ if (0 <= b->indent && (0 == len || '\n' != b->buf.head[len - 1])) {
448
+ rb_str_cat(rstr, "\n", 1);
449
+ }
326
450
 
327
451
  if ('\0' != *b->encoding) {
328
452
  rb_enc_associate(rstr, rb_enc_find(b->encoding));
@@ -396,7 +520,7 @@ static VALUE builder_file(int argc, VALUE *argv, VALUE self) {
396
520
  rb_raise(ox_arg_error_class, "missing filename");
397
521
  }
398
522
  Check_Type(*argv, T_STRING);
399
- if (NULL == (f = fopen(StringValuePtr(*argv), "w"))) {
523
+ if (NULL == (f = fopen(StringValuePtr(*argv), "wb"))) {
400
524
  xfree(b);
401
525
  rb_raise(rb_eIOError, "%s\n", strerror(errno));
402
526
  }
@@ -494,6 +618,20 @@ static VALUE builder_instruct(int argc, VALUE *argv, VALUE self) {
494
618
  Builder b;
495
619
 
496
620
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
621
+ if (0 < argc) {
622
+ volatile VALUE nsym = Qnil;
623
+ const char *nstr;
624
+ size_t nx;
625
+
626
+ check_name(*argv, &nsym, &nstr, &nx, "expected a Symbol or String");
627
+ if (1 < argc && rb_cHash == rb_obj_class(argv[1])) {
628
+ // The "<?" and the target are out by the time the values are
629
+ // written, so a raise down there would leave them in the buffer.
630
+ check_instruct_value(argv[1], ox_version_sym);
631
+ check_instruct_value(argv[1], ox_encoding_sym);
632
+ check_instruct_value(argv[1], ox_standalone_sym);
633
+ }
634
+ }
497
635
  i_am_a_child(b, false);
498
636
  append_indent(b);
499
637
  if (0 == argc) {
@@ -562,33 +700,29 @@ static VALUE builder_instruct(int argc, VALUE *argv, VALUE self) {
562
700
  * - +attributes+ - (Hash) of the element
563
701
  */
564
702
  static VALUE builder_element(int argc, VALUE *argv, VALUE self) {
565
- Builder b;
566
- Element e;
567
- const char *name;
568
- long len;
703
+ Builder b;
704
+ Element e;
705
+ volatile VALUE sym = Qnil;
706
+ size_t xsize = 0;
707
+ const char *name;
708
+ long len;
569
709
 
570
710
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
571
711
 
572
712
  if (1 > argc) {
573
713
  rb_raise(ox_arg_error_class, "missing element name");
574
714
  }
715
+ // Everything that can raise has to run before the first write, or the raise
716
+ // leaves an element started that no later call can finish or take back. It
717
+ // also has to run before b->depth++, or the stack slot is never filled in.
718
+ len = check_name(*argv, &sym, &name, &xsize, "expected a Symbol or String for an element name");
719
+ check_name_chars(name, (size_t)len, false);
720
+ if (MAX_DEPTH <= b->depth + 1) {
721
+ rb_raise(ox_arg_error_class, "XML too deeply nested");
722
+ }
575
723
  i_am_a_child(b, false);
576
724
  append_indent(b);
577
725
  b->depth++;
578
- if (MAX_DEPTH <= b->depth) {
579
- rb_raise(ox_arg_error_class, "XML too deeply nested");
580
- }
581
- switch (rb_type(*argv)) {
582
- case T_STRING:
583
- name = StringValuePtr(*argv);
584
- len = RSTRING_LEN(*argv);
585
- break;
586
- case T_SYMBOL:
587
- name = rb_id2name(SYM2ID(*argv));
588
- len = strlen(name);
589
- break;
590
- default: rb_raise(ox_arg_error_class, "expected a Symbol or String for an element name"); break;
591
- }
592
726
  e = &b->stack[b->depth];
593
727
  if (sizeof(e->buf) <= (size_t)len) {
594
728
  e->name = strdup(name);
@@ -604,7 +738,7 @@ static VALUE builder_element(int argc, VALUE *argv, VALUE self) {
604
738
  buf_append(&b->buf, '<');
605
739
  b->col++;
606
740
  b->pos++;
607
- append_string(b, e->name, len, xml_element_chars, false);
741
+ append_string(b, e->name, len, xml_element_chars, xsize, false);
608
742
  if (1 < argc && T_HASH == rb_type(argv[1])) {
609
743
  rb_hash_foreach(argv[1], append_attr, (VALUE)b);
610
744
  }
@@ -624,32 +758,25 @@ static VALUE builder_element(int argc, VALUE *argv, VALUE self) {
624
758
  * - +attributes+ - (Hash) of the element
625
759
  */
626
760
  static VALUE builder_void_element(int argc, VALUE *argv, VALUE self) {
627
- Builder b;
628
- const char *name;
629
- long len;
761
+ Builder b;
762
+ volatile VALUE sym = Qnil;
763
+ size_t xsize = 0;
764
+ const char *name;
765
+ long len;
630
766
 
631
767
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
632
768
 
633
769
  if (1 > argc) {
634
770
  rb_raise(ox_arg_error_class, "missing element name");
635
771
  }
772
+ len = check_name(*argv, &sym, &name, &xsize, "expected a Symbol or String for an element name");
773
+ check_name_chars(name, (size_t)len, false);
636
774
  i_am_a_child(b, false);
637
775
  append_indent(b);
638
- switch (rb_type(*argv)) {
639
- case T_STRING:
640
- name = StringValuePtr(*argv);
641
- len = RSTRING_LEN(*argv);
642
- break;
643
- case T_SYMBOL:
644
- name = rb_id2name(SYM2ID(*argv));
645
- len = strlen(name);
646
- break;
647
- default: rb_raise(ox_arg_error_class, "expected a Symbol or String for an element name"); break;
648
- }
649
776
  buf_append(&b->buf, '<');
650
777
  b->col++;
651
778
  b->pos++;
652
- append_string(b, name, len, xml_element_chars, false);
779
+ append_string(b, name, len, xml_element_chars, xsize, false);
653
780
  if (1 < argc && T_HASH == rb_type(argv[1])) {
654
781
  rb_hash_foreach(argv[1], append_attr, (VALUE)b);
655
782
  }
@@ -668,18 +795,22 @@ static VALUE builder_void_element(int argc, VALUE *argv, VALUE self) {
668
795
  */
669
796
  static VALUE builder_comment(VALUE self, VALUE text) {
670
797
  Builder b;
798
+ size_t size;
799
+ size_t xsize;
671
800
 
672
801
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
673
802
  rb_check_type(text, T_STRING);
803
+ size = (size_t)RSTRING_LEN(text);
804
+ xsize = check_string(StringValuePtr(text), size, xml_element_chars);
674
805
  i_am_a_child(b, false);
675
806
  append_indent(b);
676
807
  buf_append_string(&b->buf, "<!--", 4);
677
- b->col += 5;
678
- b->pos += 5;
679
- append_string(b, StringValuePtr(text), RSTRING_LEN(text), xml_element_chars, false);
808
+ b->col += 4;
809
+ b->pos += 4;
810
+ append_string(b, StringValuePtr(text), size, xml_element_chars, xsize, false);
680
811
  buf_append_string(&b->buf, "-->", 3);
681
- b->col += 5;
682
- b->pos += 5;
812
+ b->col += 3;
813
+ b->pos += 3;
683
814
 
684
815
  return Qnil;
685
816
  }
@@ -691,15 +822,19 @@ static VALUE builder_comment(VALUE self, VALUE text) {
691
822
  */
692
823
  static VALUE builder_doctype(VALUE self, VALUE text) {
693
824
  Builder b;
825
+ size_t size;
826
+ size_t xsize;
694
827
 
695
828
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
696
829
  rb_check_type(text, T_STRING);
830
+ size = (size_t)RSTRING_LEN(text);
831
+ xsize = check_string(StringValuePtr(text), size, xml_element_chars);
697
832
  i_am_a_child(b, false);
698
833
  append_indent(b);
699
834
  buf_append_string(&b->buf, "<!DOCTYPE ", 10);
700
835
  b->col += 10;
701
836
  b->pos += 10;
702
- append_string(b, StringValuePtr(text), RSTRING_LEN(text), xml_element_chars, false);
837
+ append_string(b, StringValuePtr(text), size, xml_element_chars, xsize, false);
703
838
  buf_append(&b->buf, '>');
704
839
  b->col++;
705
840
  b->pos++;
@@ -717,6 +852,8 @@ static VALUE builder_text(int argc, VALUE *argv, VALUE self) {
717
852
  Builder b;
718
853
  volatile VALUE v;
719
854
  volatile VALUE strip_invalid_chars;
855
+ size_t size;
856
+ size_t xsize;
720
857
 
721
858
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
722
859
 
@@ -730,9 +867,14 @@ static VALUE builder_text(int argc, VALUE *argv, VALUE self) {
730
867
  strip_invalid_chars = Qfalse;
731
868
  }
732
869
 
733
- v = rb_String(v);
870
+ v = rb_String(v);
871
+ size = (size_t)RSTRING_LEN(v);
872
+ // Stripping is allowed to drop the byte, so it is the one writer that does
873
+ // not have to refuse the value up front.
874
+ xsize = RTEST(strip_invalid_chars) ? xml_str_len((const unsigned char *)StringValuePtr(v), size, xml_element_chars)
875
+ : check_string(StringValuePtr(v), size, xml_element_chars);
734
876
  i_am_a_child(b, true);
735
- append_string(b, StringValuePtr(v), RSTRING_LEN(v), xml_element_chars, RTEST(strip_invalid_chars));
877
+ append_string(b, StringValuePtr(v), size, xml_element_chars, xsize, RTEST(strip_invalid_chars));
736
878
 
737
879
  return Qnil;
738
880
  }
@@ -748,29 +890,42 @@ static VALUE builder_cdata(VALUE self, VALUE data) {
748
890
  const char *str;
749
891
  const char *s;
750
892
  const char *end;
893
+ const char *from;
894
+ const char *split;
895
+ size_t extra = 0;
751
896
  int len;
752
897
 
753
898
  TypedData_Get_Struct(self, struct _builder, &ox_builder_type, b);
754
899
 
755
- v = rb_String(v);
756
- str = StringValuePtr(v);
757
- len = (int)RSTRING_LEN(v);
758
- s = str;
759
- end = str + len;
900
+ v = rb_String(v);
901
+ str = StringValuePtr(v);
902
+ len = (int)RSTRING_LEN(v);
903
+ s = str;
904
+ end = str + len;
905
+ from = str;
906
+ check_unescaped(str, (size_t)len);
760
907
  i_am_a_child(b, false);
761
908
  append_indent(b);
762
909
  buf_append_string(&b->buf, "<![CDATA[", 9);
763
910
  b->col += 9;
764
911
  b->pos += 9;
765
- buf_append_string(&b->buf, str, len);
766
- b->col += len;
912
+ // See xml_cdata_end(). The ']]' stays in this section and the '>' opens the
913
+ // next one, so from lands on the '>' each time round.
914
+ while (NULL != (split = xml_cdata_end(from, end))) {
915
+ buf_append_string(&b->buf, from, (split + 2) - from);
916
+ buf_append_string(&b->buf, "]]><![CDATA[", CDATA_SPLIT_EXTRA);
917
+ extra += CDATA_SPLIT_EXTRA;
918
+ from = split + 2;
919
+ }
920
+ buf_append_string(&b->buf, from, end - from);
921
+ b->col += len + extra;
767
922
  s = strchr(s, '\n');
768
923
  while (NULL != s) {
769
924
  b->line++;
770
925
  b->col = end - s;
771
926
  s = strchr(s + 1, '\n');
772
927
  }
773
- b->pos += len;
928
+ b->pos += len + extra;
774
929
  buf_append_string(&b->buf, "]]>", 3);
775
930
  b->col += 3;
776
931
  b->pos += 3;