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/sax_as.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 <strings.h>
@@ -18,19 +19,26 @@
18
19
  #include "ruby.h"
19
20
  #include "ruby/version.h"
20
21
  #include "sax.h"
22
+ #include "time_conv.h"
21
23
 
22
24
  static VALUE parse_double_time(const char *text) {
23
- long v = 0;
25
+ time_t v = 0;
24
26
  long v2 = 0;
25
27
  const char *dot = 0;
26
28
  char c;
29
+ bool neg = false;
27
30
 
31
+ // A time before the epoch is written as a sign followed by the magnitude.
32
+ if ('-' == *text) {
33
+ neg = true;
34
+ text++;
35
+ }
28
36
  for (; '.' != *text; text++) {
29
37
  c = *text;
30
38
  if (c < '0' || '9' < c) {
31
39
  return Qnil;
32
40
  }
33
- v = 10 * v + (long)(c - '0');
41
+ v = 10 * v + (time_t)(c - '0');
34
42
  }
35
43
  dot = text++;
36
44
  for (; '\0' != *text && text - dot <= 6; text++) {
@@ -43,6 +51,17 @@ static VALUE parse_double_time(const char *text) {
43
51
  for (; text - dot <= 9; text++) {
44
52
  v2 *= 10;
45
53
  }
54
+ if (neg) {
55
+ // rb_time_nano_new() wants a non negative nanosecond count, so the
56
+ // fraction goes back into the second count the way dump_time_thin()
57
+ // took it out.
58
+ if (0 == v2) {
59
+ v = -v;
60
+ } else {
61
+ v = -v - 1;
62
+ v2 = 1000000000L - v2;
63
+ }
64
+ }
46
65
  return rb_time_nano_new(v, v2);
47
66
  }
48
67
 
@@ -69,7 +88,9 @@ static VALUE parse_xsd_time(const char *text) {
69
88
  {2, '\0', '\0'},
70
89
  {0, '\0', '\0'}};
71
90
  Tp tp = tpa;
72
- struct tm tm;
91
+ long nsec = 0;
92
+ long offset;
93
+ bool neg = false;
73
94
 
74
95
  memset(cargs, 0, sizeof(cargs));
75
96
  for (; 0 != tp->cnt; tp++) {
@@ -83,6 +104,15 @@ static VALUE parse_xsd_time(const char *text) {
83
104
  }
84
105
  v = 10 * v + (long)(c - '0');
85
106
  }
107
+ // The fraction is the only field terminated by the offset sign, and the
108
+ // sign is the only place the offset's direction is written down.
109
+ if ('+' == tp->end) {
110
+ nsec = v;
111
+ for (; 0 < i; i--) {
112
+ nsec *= 10;
113
+ }
114
+ neg = ('-' == *text);
115
+ }
86
116
  if ('\0' == c) {
87
117
  break;
88
118
  }
@@ -92,13 +122,15 @@ static VALUE parse_xsd_time(const char *text) {
92
122
  }
93
123
  *cp++ = v;
94
124
  }
95
- tm.tm_year = (int)cargs[0] - 1900;
96
- tm.tm_mon = (int)cargs[1] - 1;
97
- tm.tm_mday = (int)cargs[2];
98
- tm.tm_hour = (int)cargs[3];
99
- tm.tm_min = (int)cargs[4];
100
- tm.tm_sec = (int)cargs[5];
101
- return rb_time_nano_new(mktime(&tm), cargs[6]);
125
+ offset = cargs[7] * 3600 + cargs[8] * 60;
126
+ if (neg) {
127
+ offset = -offset;
128
+ }
129
+ // mktime() would read the wall clock as local time and throw the offset
130
+ // away, and returns -1 for everything before the epoch on Windows.
131
+ return rb_time_nano_new(
132
+ (time_t)(ox_epoch_from_civil(cargs[0], cargs[1], cargs[2], cargs[3], cargs[4], cargs[5]) - offset),
133
+ nsec);
102
134
  }
103
135
 
104
136
  /* call-seq: as_s()
data/ext/ox/sax_buf.c CHANGED
@@ -133,14 +133,22 @@ static VALUE partial_io_cb(VALUE rbuf) {
133
133
  VALUE args[1];
134
134
  VALUE rstr;
135
135
  char *str;
136
+ size_t max = (size_t)(buf->end - buf->tail);
136
137
  size_t cnt;
137
138
 
138
- args[0] = ULONG2NUM(buf->end - buf->tail);
139
+ args[0] = ULONG2NUM(max);
139
140
  rstr = rb_funcall2(buf->in.io, ox_readpartial_id, 1, args);
140
141
  str = StringValuePtr(rstr);
141
- cnt = strlen(str);
142
+ // Clamp to the space actually requested. A misbehaving IO can return more
143
+ // than max bytes; copying it unclamped overflows the buffer (which starts
144
+ // on the C stack). Use the real byte length, not strlen(), so an embedded
145
+ // NUL does not cause a short copy that still leaves a longer string.
146
+ cnt = (size_t)RSTRING_LEN(rstr);
147
+ if (cnt > max) {
148
+ cnt = max;
149
+ }
142
150
  // printf("*** read partial %lu bytes, str: '%s'\n", cnt, str);
143
- strcpy(buf->tail, str);
151
+ memcpy(buf->tail, str, cnt);
144
152
  buf->read_end = buf->tail + cnt;
145
153
 
146
154
  return Qtrue;
@@ -151,14 +159,19 @@ static VALUE io_cb(VALUE rbuf) {
151
159
  VALUE args[1];
152
160
  VALUE rstr;
153
161
  char *str;
162
+ size_t max = (size_t)(buf->end - buf->tail);
154
163
  size_t cnt;
155
164
 
156
- args[0] = ULONG2NUM(buf->end - buf->tail);
165
+ args[0] = ULONG2NUM(max);
157
166
  rstr = rb_funcall2(buf->in.io, ox_read_id, 1, args);
158
167
  str = StringValuePtr(rstr);
159
- cnt = strlen(str);
168
+ // See partial_io_cb: clamp to the requested size to prevent overflow.
169
+ cnt = (size_t)RSTRING_LEN(rstr);
170
+ if (cnt > max) {
171
+ cnt = max;
172
+ }
160
173
  // printf("*** read %lu bytes, str: '%s'\n", cnt, str);
161
- strcpy(buf->tail, str);
174
+ memcpy(buf->tail, str, cnt);
162
175
  buf->read_end = buf->tail + cnt;
163
176
 
164
177
  return Qtrue;
@@ -180,9 +193,11 @@ static int read_from_fd(Buf buf) {
180
193
  if (cnt < 0) {
181
194
  ox_sax_drive_error(buf->dr, "failed to read from file");
182
195
  return -1;
183
- } else if (0 != cnt) {
184
- buf->read_end = buf->tail + cnt;
196
+ } else if (0 == cnt) { // EOF
197
+ return -1;
185
198
  }
199
+ buf->read_end = buf->tail + cnt;
200
+
186
201
  return 0;
187
202
  }
188
203
 
data/ext/ox/sax_stack.h CHANGED
@@ -13,6 +13,7 @@
13
13
 
14
14
  #define STACK_INC 32
15
15
  #define NV_BUF_MAX 64
16
+ #define MAX_ELEMENT_DEPTH 1000
16
17
 
17
18
  typedef struct _nv {
18
19
  char name_buf[NV_BUF_MAX];
@@ -40,6 +41,15 @@ inline static int stack_empty(NStack stack) {
40
41
  }
41
42
 
42
43
  inline static void stack_cleanup(NStack stack) {
44
+ Nv nv;
45
+
46
+ // Elements still on the stack were never popped, so their over-long names
47
+ // are still owned here.
48
+ for (nv = stack->head; nv < stack->tail; nv++) {
49
+ if (NULL != nv->name) {
50
+ xfree((char *)(nv->name));
51
+ }
52
+ }
43
53
  if (stack->base != stack->head) {
44
54
  xfree(stack->head);
45
55
  }
@@ -50,6 +60,9 @@ inline static void stack_push(NStack stack, const char *name, size_t nlen, VALUE
50
60
  size_t len = stack->end - stack->head;
51
61
  size_t toff = stack->tail - stack->head;
52
62
 
63
+ if (MAX_ELEMENT_DEPTH < toff) {
64
+ rb_raise(ox_parse_error_class, "sax nesting depth exceeded.\n");
65
+ }
53
66
  if (stack->base == stack->head) {
54
67
  stack->head = ALLOC_N(struct _nv, len + STACK_INC);
55
68
  memcpy(stack->head, stack->base, sizeof(struct _nv) * len);
@@ -0,0 +1,62 @@
1
+ /* time_conv.h
2
+ * Copyright (c) 2011, Peter Ohler
3
+ * All rights reserved.
4
+ */
5
+
6
+ #ifndef OX_TIME_CONV_H
7
+ #define OX_TIME_CONV_H
8
+
9
+ #include <stdint.h>
10
+ #include <time.h>
11
+
12
+ // Howard Hinnant's civil_from_days and days_from_civil, which are inverses.
13
+ // Neither consults a timezone database, and that is the point: mktime() reads
14
+ // the wall clock as local time no matter what offset the document carries, and
15
+ // the Microsoft CRT returns -1 for anything before the epoch.
16
+
17
+ // Splits seconds since the epoch into a UTC date and wall clock.
18
+ inline static void
19
+ ox_civil_from_epoch(int64_t sec, long long *yearp, int *monp, int *dayp, int *hourp, int *minp, int *secp) {
20
+ long long days = (long long)sec / 86400;
21
+ long long rem = (long long)sec % 86400;
22
+ long long era, doe, yoe, doy, mp;
23
+
24
+ if (0 > rem) {
25
+ rem += 86400;
26
+ days--;
27
+ }
28
+ *hourp = (int)(rem / 3600);
29
+ *minp = (int)(rem % 3600 / 60);
30
+ *secp = (int)(rem % 60);
31
+
32
+ // The era starts in March so the leap day is last in the year and needs no
33
+ // special case.
34
+ days += 719468;
35
+ era = (0 <= days ? days : days - 146096) / 146097;
36
+ doe = days - era * 146097;
37
+ yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
38
+ doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
39
+ mp = (5 * doy + 2) / 153;
40
+ *dayp = (int)(doy - (153 * mp + 2) / 5 + 1);
41
+ *monp = (int)(10 > mp ? mp + 3 : mp - 9);
42
+ *yearp = yoe + era * 400 + (2 >= *monp);
43
+ }
44
+
45
+ // Joins a UTC date and wall clock back into seconds since the epoch.
46
+ inline static int64_t ox_epoch_from_civil(long year, long mon, long day, long hour, long min, long sec) {
47
+ int64_t y = (int64_t)year;
48
+ int64_t era, yoe, doy, doe, days;
49
+
50
+ if (2 >= mon) {
51
+ y--;
52
+ }
53
+ era = (0 <= y ? y : y - 399) / 400;
54
+ yoe = y - era * 400;
55
+ doy = (153 * (mon + (2 < mon ? -3 : 9)) + 2) / 5 + day - 1;
56
+ doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
57
+ days = era * 146097 + doe - 719468;
58
+
59
+ return days * 86400 + (int64_t)hour * 3600 + (int64_t)min * 60 + (int64_t)sec;
60
+ }
61
+
62
+ #endif /* OX_TIME_CONV_H */
@@ -0,0 +1,58 @@
1
+ /* xml_check.h
2
+ * Copyright (c) 2011, Peter Ohler
3
+ * All rights reserved.
4
+ */
5
+
6
+ #ifndef OX_XML_CHECK_H
7
+ #define OX_XML_CHECK_H
8
+
9
+ #include <stdbool.h>
10
+ #include <stddef.h>
11
+
12
+ #include "err.h"
13
+ #include "xml_str.h"
14
+
15
+ /* What a writer asks before it writes anything, shared by dump.c and builder.c.
16
+ * The scans are in xml_str.h, which stays free of Ruby so it can be reasoned
17
+ * about on its own; these are the wrappers that turn an answer into a raise.
18
+ */
19
+
20
+ /* Raises on a character XML has no way to write, for a value that is copied
21
+ * through rather than escaped. The escape path answers the same question on its
22
+ * way past, but inside a CDATA section, a comment or an instruction a character
23
+ * reference is not expanded, so escaping is not an option for those.
24
+ */
25
+ inline static void check_unescaped(const char *str, size_t size) {
26
+ const unsigned char *bad = xml_first_invalid((const unsigned char *)str, size);
27
+
28
+ if (NULL != bad) {
29
+ rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", *bad);
30
+ }
31
+ }
32
+
33
+ /* Refuses a name only for a character that could end it where it is written,
34
+ * the narrow rule #469 settled on, since anything else is odd but round trips.
35
+ * A byte XML can not hold at all keeps the message the values raise with.
36
+ *
37
+ * Escaping is not the answer here even where a table has an escape: a name is
38
+ * not a place a character reference is expanded, so &lt; in a name reads back
39
+ * as four more characters rather than as the one that was asked for.
40
+ */
41
+ inline static void check_name_chars(const char *str, size_t size, bool attr) {
42
+ const char ends = attr ? XML_NAME_ATTR : XML_NAME_ELEMENT;
43
+ const unsigned char *bad = xml_first_bad_name((const unsigned char *)str, size, ends);
44
+ const char *kind = attr ? "an attribute" : "an element";
45
+
46
+ if (NULL == bad) {
47
+ return;
48
+ }
49
+ if (0x20 > *bad && '\t' != *bad && '\n' != *bad && '\r' != *bad) {
50
+ rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", *bad);
51
+ }
52
+ if (0x20 >= *bad) {
53
+ rb_raise(ox_syntax_error_class, "'\\#x%02x' can not be used in %s name.", *bad, kind);
54
+ }
55
+ rb_raise(ox_syntax_error_class, "'%c' can not be used in %s name.", *bad, kind);
56
+ }
57
+
58
+ #endif /* OX_XML_CHECK_H */
data/ext/ox/xml_str.h ADDED
@@ -0,0 +1,247 @@
1
+ /* xml_str.h
2
+ * Copyright (c) 2011, Peter Ohler
3
+ * All rights reserved.
4
+ */
5
+
6
+ #ifndef OX_XML_STR_H
7
+ #define OX_XML_STR_H
8
+
9
+ #include <stdint.h>
10
+ #include <string.h>
11
+
12
+ #ifndef __has_builtin
13
+ #define __has_builtin(x) 0
14
+ #endif
15
+
16
+ #if __has_builtin(__builtin_memcpy)
17
+ #define HAVE_FAST_MEMCPY 1
18
+
19
+ inline static void fast_memcpy16(void *dest, const void *src, size_t n) {
20
+ char *d = (char *)dest;
21
+ const char *s = (const char *)src;
22
+
23
+ if (n >= 8) {
24
+ __builtin_memcpy(d, s, 8);
25
+ __builtin_memcpy(d + n - 8, s + n - 8, 8);
26
+ } else if (n >= 4) {
27
+ __builtin_memcpy(d, s, 4);
28
+ __builtin_memcpy(d + n - 4, s + n - 4, 4);
29
+ } else if (n >= 2) {
30
+ __builtin_memcpy(d, s, 2);
31
+ __builtin_memcpy(d + n - 2, s + n - 2, 2);
32
+ } else if (n >= 1) {
33
+ *d = *s;
34
+ }
35
+ }
36
+ #endif
37
+
38
+ /* Shared helpers for the serializer escape path used by both dump.c and
39
+ * builder.c. The size scan and the escape loop both walk the source string a
40
+ * word at a time and only fall to a byte at a time when a word contains a byte
41
+ * that might need escaping.
42
+ *
43
+ * Each escape table maps a byte to the length of its serialized form held as an
44
+ * ASCII digit, '1' meaning the byte is copied through unchanged. The tables in
45
+ * use (xml_element_chars, xml_quote_chars / xml_attr_chars) escape only a
46
+ * subset of
47
+ *
48
+ * a byte below 0x20, '"', '\'', '&', '<', '>'
49
+ *
50
+ * so a word none of whose bytes are in that set is entirely pass-through and
51
+ * every one of its bytes is '1' in every table. The predicate below is that
52
+ * union. It is deliberately a superset: '"' and '\'' are pass-through in
53
+ * xml_element_chars, and 0x09, 0x0a and 0x0d are pass-through in every table,
54
+ * but flagging them only sends those bytes to the byte loop, which copies them
55
+ * through unchanged, so the output is identical. It must never miss a byte a
56
+ * table would escape, which is why the whole escaped union is covered even
57
+ * though no current table escapes '\''.
58
+ */
59
+
60
+ #define XSTR_ONES 0x0101010101010101ULL
61
+ #define XSTR_HIGH 0x8080808080808080ULL
62
+
63
+ /* Sets the high bit of every byte of a word that is not guaranteed to be
64
+ * pass-through, that is a byte below 0x20, or a '"', '\'', '&', '<' or '>'.
65
+ *
66
+ * The byte below 0x20 test is the classic (v - ones*0x20) & ~v hasless, and the
67
+ * five character tests are haszero on v xored with the character broadcast to
68
+ * every byte. Bytes with the high bit already set, the UTF-8 lead and
69
+ * continuation bytes, are cleared by the & ~... term and so are never flagged.
70
+ *
71
+ * A borrow out of one byte can also flag the byte above it, but a borrow is
72
+ * only produced by a byte that already matched, so the lowest flagged byte is
73
+ * always a real match and there are never false negatives. That is what lets a
74
+ * zero result mean the whole word is pass-through, and the count of trailing
75
+ * zeros point at the first byte that has to go through the table.
76
+ */
77
+ inline static uint64_t xml_bytes_of_interest(uint64_t v) {
78
+ uint64_t q = v ^ (XSTR_ONES * (uint64_t)'"');
79
+ uint64_t s = v ^ (XSTR_ONES * (uint64_t)'\'');
80
+ uint64_t a = v ^ (XSTR_ONES * (uint64_t)'&');
81
+ uint64_t l = v ^ (XSTR_ONES * (uint64_t)'<');
82
+ uint64_t g = v ^ (XSTR_ONES * (uint64_t)'>');
83
+
84
+ return (((v - XSTR_ONES * 0x20) & ~v) | ((q - XSTR_ONES) & ~q) | ((s - XSTR_ONES) & ~s) | ((a - XSTR_ONES) & ~a) |
85
+ ((l - XSTR_ONES) & ~l) | ((g - XSTR_ONES) & ~g)) &
86
+ XSTR_HIGH;
87
+ }
88
+
89
+ /* Offset of the lowest flagged byte, which is the first byte of the word that
90
+ * has to go through the table. Only ever called with a non-zero mask.
91
+ */
92
+ inline static int xml_first_of_interest(const unsigned char *s, uint64_t mask) {
93
+ #if defined(__GNUC__) || defined(__clang__)
94
+ #if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
95
+ (void)s;
96
+ return (int)(__builtin_clzll(mask) >> 3);
97
+ #else
98
+ (void)s;
99
+ return (int)(__builtin_ctzll(mask) >> 3);
100
+ #endif
101
+ #else
102
+ int i;
103
+
104
+ (void)mask;
105
+ for (i = 0; i < 8; i++) {
106
+ unsigned char u = s[i];
107
+
108
+ if (u < 0x20 || '"' == u || '\'' == u || '&' == u || '<' == u || '>' == u) {
109
+ break;
110
+ }
111
+ }
112
+ return i;
113
+ #endif
114
+ }
115
+
116
+ /* Length of str once serialized through table, the sum of the table entries
117
+ * less len * '0' so that a pass-through byte counts as one. A word none of
118
+ * whose bytes need escaping contributes 8 * '1' with no table lookup at all;
119
+ * only words with a byte of interest are summed one byte at a time. The word
120
+ * loop is bounded by str + len so it never reads past the string.
121
+ */
122
+ inline static size_t xml_str_len(const unsigned char *str, size_t len, const char *table) {
123
+ const unsigned char *end = str + len;
124
+ size_t size = 0;
125
+
126
+ while (str + 8 <= end) {
127
+ uint64_t v;
128
+
129
+ memcpy(&v, str, 8);
130
+ if (0 == xml_bytes_of_interest(v)) {
131
+ size += 8 * (size_t)'1';
132
+ } else {
133
+ int i;
134
+
135
+ for (i = 0; i < 8; i++) {
136
+ size += table[str[i]];
137
+ }
138
+ }
139
+ str += 8;
140
+ }
141
+ for (; str < end; str++) {
142
+ size += table[*str];
143
+ }
144
+ return size - len * (size_t)'0';
145
+ }
146
+
147
+ /* First byte of str that no XML table lets through, or NULL.
148
+ *
149
+ * Every table marks the same set - the bytes under 0x20 other than 0x09, 0x0a
150
+ * and 0x0d - so this takes no table and answers for all of them. They are all
151
+ * under 0x20, which is the first term of xml_bytes_of_interest(), so a word
152
+ * that term does not flag holds none of them and is skipped whole.
153
+ *
154
+ * A caller that has to write a delimiter before the value it is escaping uses
155
+ * this to find out first, since the escape loop only reaches the byte after
156
+ * the delimiter is already out.
157
+ */
158
+ inline static const unsigned char *xml_first_invalid(const unsigned char *str, size_t len) {
159
+ const unsigned char *end = str + len;
160
+
161
+ while (str + 8 <= end) {
162
+ uint64_t v;
163
+
164
+ memcpy(&v, str, 8);
165
+ if (0 != (((v - XSTR_ONES * 0x20) & ~v) & XSTR_HIGH)) {
166
+ int i;
167
+
168
+ for (i = 0; i < 8; i++) {
169
+ unsigned char c = str[i];
170
+
171
+ if (c < 0x20 && '\t' != c && '\n' != c && '\r' != c) {
172
+ return str + i;
173
+ }
174
+ }
175
+ }
176
+ str += 8;
177
+ }
178
+ for (; str < end; str++) {
179
+ if (*str < 0x20 && '\t' != *str && '\n' != *str && '\r' != *str) {
180
+ return str;
181
+ }
182
+ }
183
+ return NULL;
184
+ }
185
+
186
+ /* What a byte does to a name, in the same shape as the escape tables. '2' ends
187
+ * a name wherever one is written - a byte at or below a space, either white
188
+ * space or one XML can not hold at all, or one of the five characters a start
189
+ * tag is read with. '1' ends only an attribute name, the quote its value is
190
+ * written in. '0' is a byte a name can hold.
191
+ */
192
+ #define XML_NAME_ELEMENT '2'
193
+ #define XML_NAME_ATTR '1'
194
+
195
+ static const char xml_name_chars[257] = "\
196
+ 22222222222222222222222222222222\
197
+ 20100020000000020000000000002220\
198
+ 00000000000000000000000000000000\
199
+ 00000000000000000000000000000000\
200
+ 00000000000000000000000000000000\
201
+ 00000000000000000000000000000000\
202
+ 00000000000000000000000000000000\
203
+ 00000000000000000000000000000000";
204
+
205
+ /* First byte of str that ends a name written where ends says it is written, or
206
+ * NULL. One load and one compare a byte, since every one of these is a name and
207
+ * a name is short enough that a word loop would spend more on its own setup
208
+ * than it saves.
209
+ */
210
+ inline static const unsigned char *xml_first_bad_name(const unsigned char *str, size_t len, char ends) {
211
+ const unsigned char *end = str + len;
212
+
213
+ for (; str < end; str++) {
214
+ if (ends <= xml_name_chars[*str]) {
215
+ return str;
216
+ }
217
+ }
218
+ return NULL;
219
+ }
220
+
221
+ /* A CDATA section ends at the first "]]>", so a value holding one closes its
222
+ * own section and the rest is read as markup. Both writers split each
223
+ * occurrence into "]]" + "]]>" + "<![CDATA[" + ">", which reads back as the
224
+ * same three characters, so this is what they scan with.
225
+ */
226
+ #define CDATA_SPLIT_EXTRA 12
227
+
228
+ inline static const char *xml_cdata_end(const char *str, const char *end) {
229
+ for (; str + 3 <= end; str++) {
230
+ if (']' == *str && ']' == str[1] && '>' == str[2]) {
231
+ return str;
232
+ }
233
+ }
234
+ return NULL;
235
+ }
236
+
237
+ inline static size_t xml_cdata_end_cnt(const char *str, const char *end) {
238
+ size_t cnt = 0;
239
+
240
+ while (NULL != (str = xml_cdata_end(str, end))) {
241
+ cnt++;
242
+ str += 2;
243
+ }
244
+ return cnt;
245
+ }
246
+
247
+ #endif /* OX_XML_STR_H */
data/lib/ox/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Ox
2
2
  # Current version of the module.
3
- VERSION = '2.14.28'
3
+ VERSION = '2.14.29'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ox
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.14.28
4
+ version: 2.14.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
@@ -72,7 +72,10 @@ files:
72
72
  - ext/ox/slotcache.h
73
73
  - ext/ox/special.c
74
74
  - ext/ox/special.h
75
+ - ext/ox/time_conv.h
75
76
  - ext/ox/type.h
77
+ - ext/ox/xml_check.h
78
+ - ext/ox/xml_str.h
76
79
  - lib/ox.rb
77
80
  - lib/ox/bag.rb
78
81
  - lib/ox/cdata.rb
@@ -122,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
125
  - !ruby/object:Gem::Version
123
126
  version: '0'
124
127
  requirements: []
125
- rubygems_version: 4.0.6
128
+ rubygems_version: 4.0.17
126
129
  specification_version: 4
127
130
  summary: A fast XML parser and object serializer.
128
131
  test_files: []