makiri 0.3.0 → 0.4.0
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/.github/workflows/conformance.yml +22 -0
- data/.github/workflows/libfuzzer.yml +83 -0
- data/.github/workflows/security.yml +88 -3
- data/.github/workflows/valgrind.yml +135 -0
- data/CHANGELOG.md +60 -2
- data/README.md +81 -77
- data/Rakefile +194 -3
- data/ext/makiri/bridge/ruby_string.c +119 -66
- data/ext/makiri/core/mkr_alloc.c +40 -3
- data/ext/makiri/core/mkr_alloc.h +27 -4
- data/ext/makiri/core/mkr_buf.c +13 -3
- data/ext/makiri/core/mkr_buf.h +80 -5
- data/ext/makiri/core/mkr_core.c +143 -0
- data/ext/makiri/core/mkr_core.h +10 -1
- data/ext/makiri/core/mkr_span.h +186 -0
- data/ext/makiri/core/mkr_utf8.c +101 -0
- data/ext/makiri/core/mkr_utf8.h +88 -0
- data/ext/makiri/extconf.rb +104 -9
- data/ext/makiri/fuzz/Makefile +95 -0
- data/ext/makiri/fuzz/check_fuzzer.cc +4 -0
- data/ext/makiri/fuzz/xml_fuzz.c +24 -0
- data/ext/makiri/fuzz/xpath_fuzz.c +109 -0
- data/ext/makiri/glue/glue.h +8 -0
- data/ext/makiri/glue/ruby_doc.c +20 -24
- data/ext/makiri/glue/ruby_html_css.c +58 -12
- data/ext/makiri/glue/ruby_html_mutate.c +11 -6
- data/ext/makiri/glue/ruby_html_node.c +3 -32
- data/ext/makiri/glue/ruby_node.c +39 -0
- data/ext/makiri/glue/ruby_xml.c +198 -16
- data/ext/makiri/glue/ruby_xml_node.c +46 -59
- data/ext/makiri/glue/ruby_xpath.c +4 -4
- data/ext/makiri/lexbor_compat/source_loc.c +14 -16
- data/ext/makiri/lexbor_compat/utf8_input.c +5 -78
- data/ext/makiri/makiri.c +45 -0
- data/ext/makiri/xml/mkr_xml.h +2 -3
- data/ext/makiri/xml/mkr_xml_chars.c +67 -97
- data/ext/makiri/xml/mkr_xml_index.c +169 -0
- data/ext/makiri/xml/mkr_xml_index.h +48 -0
- data/ext/makiri/xml/mkr_xml_mutate.c +63 -121
- data/ext/makiri/xml/mkr_xml_node.c +147 -15
- data/ext/makiri/xml/mkr_xml_node.h +71 -6
- data/ext/makiri/xml/mkr_xml_tree.c +185 -149
- data/ext/makiri/xpath/mkr_css.c +1023 -0
- data/ext/makiri/xpath/mkr_css.h +65 -0
- data/ext/makiri/xpath/mkr_xpath.c +37 -0
- data/ext/makiri/xpath/mkr_xpath.h +13 -0
- data/ext/makiri/xpath/mkr_xpath_eval_body.h +373 -90
- data/ext/makiri/xpath/mkr_xpath_funcs_body.h +249 -231
- data/ext/makiri/xpath/mkr_xpath_internal.h +89 -9
- data/ext/makiri/xpath/mkr_xpath_lex.c +94 -124
- data/ext/makiri/xpath/mkr_xpath_node_access_xml.h +6 -3
- data/ext/makiri/xpath/mkr_xpath_number.c +109 -0
- data/ext/makiri/xpath/mkr_xpath_parse.c +79 -90
- data/ext/makiri/xpath/mkr_xpath_shared.c +40 -24
- data/ext/makiri/xpath/mkr_xpath_value_body.h +50 -24
- data/lib/makiri/cdata_section.rb +1 -3
- data/lib/makiri/comment.rb +1 -3
- data/lib/makiri/document.rb +8 -0
- data/lib/makiri/element.rb +1 -3
- data/lib/makiri/processing_instruction.rb +1 -3
- data/lib/makiri/text.rb +1 -3
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/builder.rb +263 -0
- data/lib/makiri/xml/node_methods.rb +47 -0
- data/lib/makiri.rb +1 -0
- data/script/check_alloc_failures.rb +266 -0
- data/script/check_c_safety.rb +45 -2
- data/script/check_c_safety_allowlist.yml +19 -0
- data/script/check_leaks.rb +64 -0
- data/script/leaks_harness.rb +64 -0
- data/vendor/lexbor/CMakeLists.txt +6 -0
- data/vendor/lexbor/README.md +12 -0
- data/vendor/lexbor/config.cmake +1 -1
- data/vendor/lexbor/source/lexbor/core/base.h +1 -1
- data/vendor/lexbor/source/lexbor/core/config.cmake +9 -1
- data/vendor/lexbor/source/lexbor/css/selectors/pseudo_state.c +2 -3
- data/vendor/lexbor/source/lexbor/css/selectors/state.c +3 -0
- data/vendor/lexbor/source/lexbor/dom/interfaces/element.c +21 -0
- data/vendor/lexbor/source/lexbor/dom/interfaces/element.h +5 -0
- data/vendor/lexbor/source/lexbor/encoding/decode.c +33 -4
- data/vendor/lexbor/source/lexbor/html/base.h +1 -1
- data/vendor/lexbor/source/lexbor/html/interfaces/select_element.c +4 -0
- data/vendor/lexbor/source/lexbor/html/serialize.c +545 -41
- data/vendor/lexbor/source/lexbor/html/serialize.h +2 -1
- data/vendor/lexbor/source/lexbor/html/tokenizer.h +2 -2
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_body.c +1 -1
- data/vendor/lexbor/source/lexbor/html/tree.c +6 -6
- data/vendor/lexbor/source/lexbor/selectors/selectors.c +12 -3
- data/vendor/lexbor/source/lexbor/url/base.h +1 -1
- data/vendor/lexbor/source/lexbor/url/url.c +5 -2
- data/vendor/lexbor/source/lexbor/url/url.h +9 -0
- data/vendor/lexbor/version +1 -1
- metadata +19 -1
|
@@ -21,44 +21,6 @@
|
|
|
21
21
|
#define XMLNS_NS_URI "http://www.w3.org/2000/xmlns/"
|
|
22
22
|
#define LIT_LEN(s) ((uint32_t)(sizeof(s) - 1))
|
|
23
23
|
|
|
24
|
-
static int
|
|
25
|
-
slice_eq(const char *a, uint32_t al, const char *b, uint32_t bl)
|
|
26
|
-
{
|
|
27
|
-
return al == bl && (al == 0 || memcmp(a, b, al) == 0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* Validate that [s, s+len) is a well-formed XML 1.0 QName and split it into
|
|
31
|
-
* prefix:local (pfx_len 0 = no prefix). Mirrors scan_name + split_qname in the
|
|
32
|
-
* tree builder: the whole name is NameStartChar NameChar* (a colon is a NameChar),
|
|
33
|
-
* then at most one colon separates two NCNames. 0 on success, -1 if malformed. */
|
|
34
|
-
static int
|
|
35
|
-
qname_split(const char *s, uint32_t len, const char **pfx, uint32_t *pl,
|
|
36
|
-
const char **loc, uint32_t *ll)
|
|
37
|
-
{
|
|
38
|
-
if (len == 0) return -1;
|
|
39
|
-
const char *p = s, *end = s + len;
|
|
40
|
-
uint32_t cp;
|
|
41
|
-
int bl = mkr_xml_utf8_decode(p, end, &cp);
|
|
42
|
-
if (bl == 0 || !mkr_xml_is_name_start(cp)) return -1;
|
|
43
|
-
p += bl;
|
|
44
|
-
while (p < end) {
|
|
45
|
-
bl = mkr_xml_utf8_decode(p, end, &cp);
|
|
46
|
-
if (bl == 0 || !mkr_xml_is_name_char(cp)) return -1;
|
|
47
|
-
p += bl;
|
|
48
|
-
}
|
|
49
|
-
const char *colon = memchr(s, ':', len);
|
|
50
|
-
if (colon == NULL) { *pfx = s; *pl = 0; *loc = s; *ll = len; return 0; }
|
|
51
|
-
uint32_t prefix_len = (uint32_t)(colon - s);
|
|
52
|
-
const char *ls = colon + 1;
|
|
53
|
-
uint32_t local_len = len - prefix_len - 1;
|
|
54
|
-
if (prefix_len == 0 || local_len == 0) return -1; /* ":x" or "x:" */
|
|
55
|
-
if (memchr(ls, ':', local_len) != NULL) return -1; /* a second colon */
|
|
56
|
-
if (mkr_xml_utf8_decode(ls, ls + local_len, &cp) == 0 || !mkr_xml_is_name_start(cp))
|
|
57
|
-
return -1; /* local must be an NCName */
|
|
58
|
-
*pfx = s; *pl = prefix_len; *loc = ls; *ll = local_len;
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
24
|
/* Nearest in-scope binding for +prefix+ (pl 0 = the default namespace) at or
|
|
63
25
|
* above +node+, walking the real tree (no scope dictionary is threaded). */
|
|
64
26
|
static int
|
|
@@ -70,7 +32,7 @@ resolve_in_scope(const mkr_xml_node_t *node, const char *prefix, uint32_t pl,
|
|
|
70
32
|
for (mkr_xml_node_t *a = e->attrs; a != NULL; a = a->next) {
|
|
71
33
|
const char *p, *u; uint32_t apl, ul;
|
|
72
34
|
if (mkr_xml_node_xmlns_decl(a, &p, &apl, &u, &ul)
|
|
73
|
-
&&
|
|
35
|
+
&& mkr_bytes_eq(p, apl, prefix, pl)) {
|
|
74
36
|
*uri = u; *ulen = ul; return 1;
|
|
75
37
|
}
|
|
76
38
|
}
|
|
@@ -91,58 +53,45 @@ is_connected(const mkr_xml_node_t *node)
|
|
|
91
53
|
return top->type == MKR_XML_NODE_TYPE_DOCUMENT;
|
|
92
54
|
}
|
|
93
55
|
|
|
94
|
-
/* Resolve the namespace for
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* Mirrors mkr_xml_tree.c §7. */
|
|
56
|
+
/* Resolve the namespace for QName +qn+ applied at +scope+ (the element whose
|
|
57
|
+
* in-scope declarations apply). +is_attr+ selects attribute rules (xmlns*
|
|
58
|
+
* declarations live in the xmlns namespace, an unprefixed attribute is in no
|
|
59
|
+
* namespace). +connected+ decides what an unbound prefix means: an error in the
|
|
60
|
+
* live tree, deferred (unresolved) when detached. Mirrors mkr_xml_tree.c §7. */
|
|
100
61
|
static mkr_xml_mut_status_t
|
|
101
|
-
resolve_ns(const mkr_xml_node_t *scope, const
|
|
102
|
-
const char
|
|
103
|
-
const char **uri, uint32_t *ulen)
|
|
62
|
+
resolve_ns(const mkr_xml_node_t *scope, const mkr_xml_qname_t *qn, int is_attr,
|
|
63
|
+
int connected, const char **uri, uint32_t *ulen)
|
|
104
64
|
{
|
|
105
65
|
*uri = NULL; *ulen = 0;
|
|
106
|
-
if (is_attr) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (is_default_decl || is_pfx_decl) {
|
|
110
|
-
*uri = XMLNS_NS_URI; *ulen = LIT_LEN(XMLNS_NS_URI);
|
|
111
|
-
return MKR_XML_MUT_OK;
|
|
112
|
-
}
|
|
66
|
+
if (is_attr && mkr_xml_xmlns_prefix(qn->qname, qn->qname_len, NULL, NULL)) {
|
|
67
|
+
*uri = XMLNS_NS_URI; *ulen = LIT_LEN(XMLNS_NS_URI); /* an xmlns declaration */
|
|
68
|
+
return MKR_XML_MUT_OK;
|
|
113
69
|
}
|
|
114
|
-
if (
|
|
70
|
+
if (qn->prefix_len == 0) {
|
|
115
71
|
if (is_attr) return MKR_XML_MUT_OK; /* unprefixed attribute -> no namespace */
|
|
116
72
|
const char *u; uint32_t ul; /* unprefixed element -> the default ns */
|
|
117
73
|
if (resolve_in_scope(scope, "", 0, &u, &ul) && ul > 0) { *uri = u; *ulen = ul; }
|
|
118
74
|
return MKR_XML_MUT_OK;
|
|
119
75
|
}
|
|
120
|
-
if (
|
|
76
|
+
if (mkr_bytes_eq(qn->prefix, qn->prefix_len, "xml", 3)) { /* the predefined xml: prefix */
|
|
121
77
|
*uri = XML_NS_URI; *ulen = LIT_LEN(XML_NS_URI);
|
|
122
78
|
return MKR_XML_MUT_OK;
|
|
123
79
|
}
|
|
124
|
-
if (
|
|
80
|
+
if (mkr_bytes_eq(qn->prefix, qn->prefix_len, "xmlns", 5)) return MKR_XML_MUT_BAD_NAME; /* xmlns: as a normal name */
|
|
125
81
|
const char *u; uint32_t ul;
|
|
126
|
-
if (!resolve_in_scope(scope,
|
|
82
|
+
if (!resolve_in_scope(scope, qn->prefix, qn->prefix_len, &u, &ul) || ul == 0) {
|
|
127
83
|
return connected ? MKR_XML_MUT_UNBOUND_NS : MKR_XML_MUT_OK; /* defer when detached */
|
|
128
84
|
}
|
|
129
85
|
*uri = u; *ulen = ul;
|
|
130
86
|
return MKR_XML_MUT_OK;
|
|
131
87
|
}
|
|
132
88
|
|
|
133
|
-
/* Copy
|
|
134
|
-
*
|
|
135
|
-
* +name+ (from qname_split). */
|
|
89
|
+
/* Copy +qn+ into the arena and point the node's name fields at it (shared
|
|
90
|
+
* mkr_xml_qname_assign), mapping its OOM to the mutation status. */
|
|
136
91
|
static mkr_xml_mut_status_t
|
|
137
|
-
assign_qname(mkr_xml_doc_t *doc, mkr_xml_node_t *node, const
|
|
138
|
-
const char *loc, uint32_t ll, uint32_t pl)
|
|
92
|
+
assign_qname(mkr_xml_doc_t *doc, mkr_xml_node_t *node, const mkr_xml_qname_t *qn)
|
|
139
93
|
{
|
|
140
|
-
|
|
141
|
-
if (nl > 0 && q == NULL) return MKR_XML_MUT_OOM;
|
|
142
|
-
node->qname = q; node->qname_len = nl;
|
|
143
|
-
node->local = q + (size_t)(loc - name); node->local_len = ll;
|
|
144
|
-
node->prefix = q; node->prefix_len = pl;
|
|
145
|
-
return MKR_XML_MUT_OK;
|
|
94
|
+
return mkr_xml_qname_assign(doc, node, qn) == 0 ? MKR_XML_MUT_OK : MKR_XML_MUT_OOM;
|
|
146
95
|
}
|
|
147
96
|
|
|
148
97
|
void
|
|
@@ -171,23 +120,20 @@ mkr_xml_rename(mkr_xml_doc_t *doc, mkr_xml_node_t *node, const char *name, uint3
|
|
|
171
120
|
{
|
|
172
121
|
if (node->type != MKR_XML_NODE_TYPE_ELEMENT && node->type != MKR_XML_NODE_TYPE_ATTRIBUTE)
|
|
173
122
|
return MKR_XML_MUT_TYPE;
|
|
174
|
-
|
|
175
|
-
if (
|
|
123
|
+
mkr_xml_qname_t qn;
|
|
124
|
+
if (mkr_xml_qname_split(name, nlen, &qn) != 0) return MKR_XML_MUT_BAD_NAME;
|
|
176
125
|
|
|
177
126
|
int is_attr = (node->type == MKR_XML_NODE_TYPE_ATTRIBUTE);
|
|
178
127
|
const mkr_xml_node_t *scope = is_attr ? node->parent : node;
|
|
179
128
|
const char *uri; uint32_t ulen;
|
|
180
129
|
int connected = scope != NULL && is_connected(scope);
|
|
181
|
-
mkr_xml_mut_status_t st = resolve_ns(scope,
|
|
130
|
+
mkr_xml_mut_status_t st = resolve_ns(scope, &qn, is_attr, connected, &uri, &ulen);
|
|
182
131
|
if (st != MKR_XML_MUT_OK) return st;
|
|
183
132
|
|
|
184
|
-
/* Copy the new qname BEFORE writing
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
node->
|
|
188
|
-
node->local = q + (size_t)(loc - name); node->local_len = ll;
|
|
189
|
-
node->prefix = q; node->prefix_len = pl;
|
|
190
|
-
node->ns_uri = uri; node->ns_uri_len = ulen;
|
|
133
|
+
/* Copy the new qname BEFORE writing ns_uri, so an OOM leaves node intact. */
|
|
134
|
+
st = assign_qname(doc, node, &qn);
|
|
135
|
+
if (st != MKR_XML_MUT_OK) return st;
|
|
136
|
+
node->ns_uri = uri; node->ns_uri_len = ulen;
|
|
191
137
|
return MKR_XML_MUT_OK;
|
|
192
138
|
}
|
|
193
139
|
|
|
@@ -197,21 +143,22 @@ mkr_xml_set_attribute(mkr_xml_doc_t *doc, mkr_xml_node_t *el, const char *name,
|
|
|
197
143
|
{
|
|
198
144
|
if (out) *out = NULL;
|
|
199
145
|
if (el->type != MKR_XML_NODE_TYPE_ELEMENT) return MKR_XML_MUT_TYPE;
|
|
200
|
-
|
|
201
|
-
if (
|
|
146
|
+
mkr_xml_qname_t qn;
|
|
147
|
+
if (mkr_xml_qname_split(name, nlen, &qn) != 0) return MKR_XML_MUT_BAD_NAME;
|
|
202
148
|
/* A prefixed namespace declaration (xmlns:foo="") must not bind a prefix to
|
|
203
149
|
* the empty namespace: XML Namespaces 1.0 forbids it and it serializes to
|
|
204
150
|
* invalid XML. (Undeclaring the DEFAULT namespace, xmlns="", is allowed.) */
|
|
205
|
-
if (vlen == 0 &&
|
|
151
|
+
if (vlen == 0 && qn.prefix_len == 5 && mkr_bytes_eq(qn.prefix, qn.prefix_len, "xmlns", 5))
|
|
152
|
+
return MKR_XML_MUT_BAD_NS_DECL;
|
|
206
153
|
if (vlen > 0 && mkr_xml_validate_chars(val, vlen) != 0) return MKR_XML_MUT_BAD_CHARS;
|
|
207
154
|
|
|
208
155
|
const char *uri; uint32_t ulen;
|
|
209
|
-
mkr_xml_mut_status_t st = resolve_ns(el,
|
|
156
|
+
mkr_xml_mut_status_t st = resolve_ns(el, &qn, 1, is_connected(el), &uri, &ulen);
|
|
210
157
|
if (st != MKR_XML_MUT_OK) return st;
|
|
211
158
|
|
|
212
159
|
/* An existing attribute with the same raw QName -> replace its value. */
|
|
213
160
|
for (mkr_xml_node_t *a = el->attrs; a != NULL; a = a->next) {
|
|
214
|
-
if (
|
|
161
|
+
if (mkr_bytes_eq(a->qname, a->qname_len, name, nlen)) {
|
|
215
162
|
const char *nv = mkr_xml_arena_bytes(doc, val, vlen);
|
|
216
163
|
if (vlen > 0 && nv == NULL) return MKR_XML_MUT_OOM;
|
|
217
164
|
a->value = nv ? nv : ""; a->value_len = vlen;
|
|
@@ -224,7 +171,7 @@ mkr_xml_set_attribute(mkr_xml_doc_t *doc, mkr_xml_node_t *el, const char *name,
|
|
|
224
171
|
/* New attribute - allocate node + qname + value before linking. */
|
|
225
172
|
mkr_xml_node_t *attr = mkr_xml_arena_node(doc, MKR_XML_NODE_TYPE_ATTRIBUTE);
|
|
226
173
|
if (attr == NULL) return MKR_XML_MUT_OOM;
|
|
227
|
-
st = assign_qname(doc, attr,
|
|
174
|
+
st = assign_qname(doc, attr, &qn);
|
|
228
175
|
if (st != MKR_XML_MUT_OK) return st; /* abandoned in the arena; freed with doc */
|
|
229
176
|
const char *nv = mkr_xml_arena_bytes(doc, val, vlen);
|
|
230
177
|
if (vlen > 0 && nv == NULL) return MKR_XML_MUT_OOM;
|
|
@@ -249,7 +196,7 @@ mkr_xml_remove_attribute(mkr_xml_node_t *el, const char *name, uint32_t nlen)
|
|
|
249
196
|
if (el->type != MKR_XML_NODE_TYPE_ELEMENT) return 0;
|
|
250
197
|
mkr_xml_node_t *prev = NULL;
|
|
251
198
|
for (mkr_xml_node_t *a = el->attrs; a != NULL; prev = a, a = a->next) {
|
|
252
|
-
if (
|
|
199
|
+
if (mkr_bytes_eq(a->qname, a->qname_len, name, nlen)) {
|
|
253
200
|
if (prev) prev->next = a->next; else el->attrs = a->next;
|
|
254
201
|
a->next = NULL; a->parent = NULL;
|
|
255
202
|
return 1;
|
|
@@ -344,12 +291,12 @@ mkr_xml_mut_status_t
|
|
|
344
291
|
mkr_xml_new_element(mkr_xml_doc_t *doc, const char *name, uint32_t nlen, mkr_xml_node_t **out)
|
|
345
292
|
{
|
|
346
293
|
*out = NULL;
|
|
347
|
-
|
|
348
|
-
if (
|
|
349
|
-
if (
|
|
294
|
+
mkr_xml_qname_t qn;
|
|
295
|
+
if (mkr_xml_qname_split(name, nlen, &qn) != 0) return MKR_XML_MUT_BAD_NAME;
|
|
296
|
+
if (mkr_bytes_eq(qn.prefix, qn.prefix_len, "xmlns", 5)) return MKR_XML_MUT_BAD_NAME; /* xmlns: is not an element prefix */
|
|
350
297
|
mkr_xml_node_t *el = mkr_xml_arena_node(doc, MKR_XML_NODE_TYPE_ELEMENT);
|
|
351
298
|
if (el == NULL) return MKR_XML_MUT_OOM;
|
|
352
|
-
mkr_xml_mut_status_t st = assign_qname(doc, el,
|
|
299
|
+
mkr_xml_mut_status_t st = assign_qname(doc, el, &qn);
|
|
353
300
|
if (st != MKR_XML_MUT_OK) return st;
|
|
354
301
|
*out = el; /* ns_uri stays unresolved until insertion */
|
|
355
302
|
return MKR_XML_MUT_OK;
|
|
@@ -382,8 +329,8 @@ mkr_xml_new_pi(mkr_xml_doc_t *doc, const char *target, uint32_t tlen,
|
|
|
382
329
|
{
|
|
383
330
|
*out = NULL;
|
|
384
331
|
/* PITarget is an NCName (a QName with no colon) and not "xml" in any case. */
|
|
385
|
-
|
|
386
|
-
if (
|
|
332
|
+
mkr_xml_qname_t qn;
|
|
333
|
+
if (mkr_xml_qname_split(target, tlen, &qn) != 0 || qn.prefix_len != 0) return MKR_XML_MUT_BAD_NAME;
|
|
387
334
|
if (is_reserved_pi_target(target, tlen)) return MKR_XML_MUT_BAD_NAME;
|
|
388
335
|
if (dlen > 0 && mkr_xml_validate_chars(data, dlen) != 0) return MKR_XML_MUT_BAD_CHARS;
|
|
389
336
|
mkr_xml_mut_status_t seq = mkr_xml_check_value_seq(MKR_XML_NODE_TYPE_PI, data, dlen);
|
|
@@ -410,12 +357,13 @@ static mkr_xml_mut_status_t
|
|
|
410
357
|
resolve_node_ns(mkr_xml_node_t *e, int connected)
|
|
411
358
|
{
|
|
412
359
|
const char *uri; uint32_t ulen;
|
|
413
|
-
|
|
414
|
-
|
|
360
|
+
mkr_xml_qname_t eq = mkr_xml_qname_of(e);
|
|
361
|
+
mkr_xml_mut_status_t st = resolve_ns(e, &eq, 0, connected, &uri, &ulen);
|
|
415
362
|
if (st != MKR_XML_MUT_OK) return st;
|
|
416
363
|
e->ns_uri = uri; e->ns_uri_len = ulen;
|
|
417
364
|
for (mkr_xml_node_t *a = e->attrs; a != NULL; a = a->next) {
|
|
418
|
-
|
|
365
|
+
mkr_xml_qname_t aq = mkr_xml_qname_of(a);
|
|
366
|
+
st = resolve_ns(e, &aq, 1, connected, &uri, &ulen);
|
|
419
367
|
if (st != MKR_XML_MUT_OK) return st;
|
|
420
368
|
a->ns_uri = uri; a->ns_uri_len = ulen;
|
|
421
369
|
}
|
|
@@ -425,15 +373,11 @@ resolve_node_ns(mkr_xml_node_t *e, int connected)
|
|
|
425
373
|
static mkr_xml_mut_status_t
|
|
426
374
|
resolve_subtree(mkr_xml_node_t *root, int connected)
|
|
427
375
|
{
|
|
428
|
-
for (mkr_xml_node_t *cur = root; cur != NULL;) {
|
|
376
|
+
for (mkr_xml_node_t *cur = root; cur != NULL; cur = mkr_xml_preorder_next(root, cur)) {
|
|
429
377
|
if (cur->type == MKR_XML_NODE_TYPE_ELEMENT) {
|
|
430
378
|
mkr_xml_mut_status_t st = resolve_node_ns(cur, connected);
|
|
431
379
|
if (st != MKR_XML_MUT_OK) return st;
|
|
432
380
|
}
|
|
433
|
-
if (cur->first_child != NULL) { cur = cur->first_child; continue; }
|
|
434
|
-
while (cur != root && cur->next == NULL) cur = cur->parent;
|
|
435
|
-
if (cur == root) break;
|
|
436
|
-
cur = cur->next;
|
|
437
381
|
}
|
|
438
382
|
return MKR_XML_MUT_OK;
|
|
439
383
|
}
|
|
@@ -464,11 +408,8 @@ copy_one(mkr_xml_doc_t *doc, const mkr_xml_node_t *src, bool with_ns)
|
|
|
464
408
|
mkr_xml_node_t *n = mkr_xml_arena_node(doc, src->type);
|
|
465
409
|
if (n == NULL) return NULL;
|
|
466
410
|
if (src->qname != NULL && src->qname_len > 0) { /* element / attribute QName */
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
src->local_len, src->prefix_len) != MKR_XML_MUT_OK) {
|
|
470
|
-
return NULL;
|
|
471
|
-
}
|
|
411
|
+
mkr_xml_qname_t qn = mkr_xml_qname_of(src);
|
|
412
|
+
if (assign_qname(doc, n, &qn) != MKR_XML_MUT_OK) return NULL;
|
|
472
413
|
} else if (src->local != NULL && src->local_len > 0) { /* PI target */
|
|
473
414
|
const char *t = mkr_xml_arena_bytes(doc, src->local, src->local_len);
|
|
474
415
|
if (t == NULL) return NULL;
|
|
@@ -713,19 +654,20 @@ mkr_xml_mutate_selftest(void)
|
|
|
713
654
|
mkr_xml_doc_t *doc = mkr_xml_doc_new();
|
|
714
655
|
if (doc == NULL) return 1;
|
|
715
656
|
int rc = 0;
|
|
716
|
-
|
|
657
|
+
mkr_xml_qname_t qn;
|
|
717
658
|
|
|
718
|
-
/* 1. QName validation + split */
|
|
719
|
-
if (
|
|
720
|
-
if (
|
|
721
|
-
if (
|
|
722
|
-
if (
|
|
723
|
-
if (
|
|
659
|
+
/* 1. QName validation + split (the shared mkr_xml_qname_split) */
|
|
660
|
+
if (mkr_xml_qname_split("a:b", 3, &qn) != 0 || qn.prefix_len != 1 || qn.local_len != 1) { rc = 2; goto done; }
|
|
661
|
+
if (mkr_xml_qname_split("1bad", 4, &qn) == 0) { rc = 3; goto done; } /* bad NameStartChar */
|
|
662
|
+
if (mkr_xml_qname_split("a:b:c", 5, &qn) == 0) { rc = 4; goto done; } /* two colons */
|
|
663
|
+
if (mkr_xml_qname_split(":x", 2, &qn) == 0) { rc = 5; goto done; } /* empty prefix */
|
|
664
|
+
if (mkr_xml_qname_split("x:", 2, &qn) == 0) { rc = 6; goto done; } /* empty local */
|
|
724
665
|
|
|
725
666
|
/* 2. build a root element, exercise attribute set/replace */
|
|
726
667
|
mkr_xml_node_t *r = mkr_xml_arena_node(doc, MKR_XML_NODE_TYPE_ELEMENT);
|
|
727
668
|
if (r == NULL) { rc = 7; goto done; }
|
|
728
|
-
|
|
669
|
+
mkr_xml_qname_t rq = { "r", 1, "r", 0, "r", 1 };
|
|
670
|
+
if (assign_qname(doc, r, &rq) != MKR_XML_MUT_OK) { rc = 8; goto done; }
|
|
729
671
|
|
|
730
672
|
mkr_xml_node_t *at = NULL;
|
|
731
673
|
if (mkr_xml_set_attribute(doc, r, "id", 2, "x", 1, &at) != MKR_XML_MUT_OK
|
|
@@ -757,14 +699,14 @@ mkr_xml_mutate_selftest(void)
|
|
|
757
699
|
|
|
758
700
|
/* 4. the predefined xml: prefix resolves with no declaration */
|
|
759
701
|
if (mkr_xml_set_attribute(doc, r, "xml:lang", 8, "en", 2, &at) != MKR_XML_MUT_OK
|
|
760
|
-
|| !
|
|
702
|
+
|| !mkr_bytes_eq(at->ns_uri, at->ns_uri_len, XML_NS_URI, LIT_LEN(XML_NS_URI))) { rc = 13; goto done; }
|
|
761
703
|
|
|
762
704
|
/* 5. adding an xmlns:* declaration lands in the xmlns namespace and then binds
|
|
763
705
|
* a previously-unbound prefix */
|
|
764
706
|
if (mkr_xml_set_attribute(doc, r, "xmlns:p", 7, "urn:p", 5, &at) != MKR_XML_MUT_OK
|
|
765
|
-
|| !
|
|
707
|
+
|| !mkr_bytes_eq(at->ns_uri, at->ns_uri_len, XMLNS_NS_URI, LIT_LEN(XMLNS_NS_URI))) { rc = 14; goto done; }
|
|
766
708
|
if (mkr_xml_set_attribute(doc, r, "p:k", 3, "v", 1, &at) != MKR_XML_MUT_OK
|
|
767
|
-
|| !
|
|
709
|
+
|| !mkr_bytes_eq(at->ns_uri, at->ns_uri_len, "urn:p", 5)) { rc = 15; goto done; }
|
|
768
710
|
|
|
769
711
|
/* 6. remove by name (idempotent) */
|
|
770
712
|
if (mkr_xml_remove_attribute(r, "id", 2) != 1) { rc = 16; goto done; }
|
|
@@ -813,7 +755,7 @@ mkr_xml_mutate_selftest(void)
|
|
|
813
755
|
if (mkr_xml_new_element(doc, "p:c", 3, &ne) != MKR_XML_MUT_OK) { rc = 31; goto done; }
|
|
814
756
|
if (mkr_xml_insert_child(doc, pr, ne) != MKR_XML_MUT_OK
|
|
815
757
|
|| pr->first_child != ne || ne->parent != pr
|
|
816
|
-
|| !
|
|
758
|
+
|| !mkr_bytes_eq(ne->ns_uri, ne->ns_uri_len, "urn:p", 5)) { rc = 32; goto done; }
|
|
817
759
|
if (mkr_xml_insert_child(doc, ne, tx) != MKR_XML_MUT_OK || ne->first_child != tx) { rc = 33; goto done; }
|
|
818
760
|
|
|
819
761
|
/* 12. an unbound prefix is a hard error in the live tree, leaving it unchanged */
|
|
@@ -830,8 +772,8 @@ mkr_xml_mutate_selftest(void)
|
|
|
830
772
|
if (mkr_xml_insert_child(doc, wrap, inner) != MKR_XML_MUT_OK
|
|
831
773
|
|| inner->ns_uri_len != 0) { rc = 37; goto done; } /* deferred while detached */
|
|
832
774
|
if (mkr_xml_insert_child(doc, pr, wrap) != MKR_XML_MUT_OK
|
|
833
|
-
|| !
|
|
834
|
-
|| !
|
|
775
|
+
|| !mkr_bytes_eq(wrap->ns_uri, wrap->ns_uri_len, "urn:p", 5)
|
|
776
|
+
|| !mkr_bytes_eq(inner->ns_uri, inner->ns_uri_len, "urn:p", 5)) { rc = 38; goto done; }
|
|
835
777
|
|
|
836
778
|
/* 14. cycle rejection: a node cannot be inserted under its own descendant */
|
|
837
779
|
if (mkr_xml_insert_child(doc, ne, pr) != MKR_XML_MUT_CYCLE) { rc = 39; goto done; }
|
|
@@ -859,7 +801,7 @@ mkr_xml_mutate_selftest(void)
|
|
|
859
801
|
mkr_xml_node_t *imp = NULL;
|
|
860
802
|
int irc = mkr_xml_import_subtree(doc2, pr, &imp);
|
|
861
803
|
if (irc != MKR_XML_MUT_OK || imp == NULL || imp == pr
|
|
862
|
-
|| imp->
|
|
804
|
+
|| !mkr_bytes_eq(imp->qname, imp->qname_len, "pr", 2)
|
|
863
805
|
|| imp->first_child == NULL || imp->first_child == pr->first_child) { rc = 46; }
|
|
864
806
|
mkr_xml_doc_destroy(doc2);
|
|
865
807
|
if (rc != 0) goto done;
|
|
@@ -2,12 +2,39 @@
|
|
|
2
2
|
* Ruby-free. See mkr_xml_node.h and docs/xml_parser_plan.ja.md §8.1/§8.2. */
|
|
3
3
|
#include "mkr_xml_node.h"
|
|
4
4
|
#include "mkr_xml.h"
|
|
5
|
+
#include "mkr_xml_index.h"
|
|
5
6
|
#include "../core/mkr_core.h"
|
|
6
7
|
|
|
7
8
|
#include <stddef.h> /* max_align_t */
|
|
8
9
|
#include <stdlib.h>
|
|
9
10
|
#include <string.h>
|
|
10
11
|
|
|
12
|
+
/* ASan red-zoning for the bump arena. Like Lexbor's mraw, our arena carves many
|
|
13
|
+
* sub-allocations out of one malloc'd chunk, so a write past one cut into the
|
|
14
|
+
* next stays inside that single malloc and is INVISIBLE to a plain ASan build
|
|
15
|
+
* (the heap red-zones only guard the chunk's outer boundary - this is exactly
|
|
16
|
+
* how the v3.0.0 :lexbor-contains overflow hid). So we poison each fresh chunk
|
|
17
|
+
* and unpoison only the bytes a cut actually hands out, leaving the alignment
|
|
18
|
+
* tail and not-yet-cut space poisoned: an intra-arena overrun then writes into
|
|
19
|
+
* poisoned memory and ASan reports it. Auto-active whenever our ext is built
|
|
20
|
+
* with -fsanitize=address (rake sanitize / fuzz:sanitize) - no extra flag,
|
|
21
|
+
* because unlike vendored Lexbor this is our own TU. A no-op otherwise. */
|
|
22
|
+
#if defined(__SANITIZE_ADDRESS__)
|
|
23
|
+
# define MKR_XML_HAVE_ASAN 1
|
|
24
|
+
#elif defined(__has_feature)
|
|
25
|
+
# if __has_feature(address_sanitizer)
|
|
26
|
+
# define MKR_XML_HAVE_ASAN 1
|
|
27
|
+
# endif
|
|
28
|
+
#endif
|
|
29
|
+
#if defined(MKR_XML_HAVE_ASAN)
|
|
30
|
+
# include <sanitizer/asan_interface.h>
|
|
31
|
+
# define MKR_ARENA_POISON(p, n) __asan_poison_memory_region((p), (n))
|
|
32
|
+
# define MKR_ARENA_UNPOISON(p, n) __asan_unpoison_memory_region((p), (n))
|
|
33
|
+
#else
|
|
34
|
+
# define MKR_ARENA_POISON(p, n) ((void)(p), (void)(n))
|
|
35
|
+
# define MKR_ARENA_UNPOISON(p, n) ((void)(p), (void)(n))
|
|
36
|
+
#endif
|
|
37
|
+
|
|
11
38
|
/* Alignment for arena cuts: the strictest fundamental alignment, so any object
|
|
12
39
|
* fits. The chunk payload starts at an aligned offset past the header, and every
|
|
13
40
|
* cut size is rounded up to ARENA_ALIGN, so each returned pointer is aligned. */
|
|
@@ -37,9 +64,12 @@ void
|
|
|
37
64
|
mkr_xml_doc_destroy(mkr_xml_doc_t *doc)
|
|
38
65
|
{
|
|
39
66
|
if (doc == NULL) return;
|
|
67
|
+
mkr_xml_name_index_invalidate(doc); /* free the heap-side element-name index */
|
|
40
68
|
/* whole-arena free: no individual node/byte free anywhere (read-only). */
|
|
69
|
+
const size_t hdr = align_up(sizeof(mkr_xml_arena_chunk_t), ARENA_ALIGN);
|
|
41
70
|
for (mkr_xml_arena_chunk_t *c = doc->chunks; c != NULL; ) {
|
|
42
71
|
mkr_xml_arena_chunk_t *n = c->next;
|
|
72
|
+
MKR_ARENA_UNPOISON((char *)c, hdr + c->cap); /* hand clean memory back to ASan's allocator */
|
|
43
73
|
free(c);
|
|
44
74
|
c = n;
|
|
45
75
|
}
|
|
@@ -98,15 +128,23 @@ arena_alloc(mkr_xml_doc_t *doc, size_t size)
|
|
|
98
128
|
nc->cap = cap;
|
|
99
129
|
doc->chunks = nc;
|
|
100
130
|
c = nc;
|
|
131
|
+
/* poison the whole payload; each cut unpoisons only what it returns. */
|
|
132
|
+
MKR_ARENA_POISON((char *)nc + hdr, cap);
|
|
101
133
|
}
|
|
102
134
|
void *p = (char *)c + hdr + c->used;
|
|
103
135
|
c->used += need;
|
|
104
136
|
doc->arena_bytes += need;
|
|
137
|
+
/* Hand out exactly +size+ usable bytes; the [size, need) alignment tail (and
|
|
138
|
+
* everything past it in the chunk) stays poisoned as a red-zone, so a write
|
|
139
|
+
* one byte past the request is caught instead of silently clobbering the
|
|
140
|
+
* next cut. ASan tolerates the sub-granule trailing byte (ARENA_ALIGN cuts
|
|
141
|
+
* keep each slot on an 8-byte shadow boundary). */
|
|
142
|
+
MKR_ARENA_UNPOISON(p, size);
|
|
105
143
|
return p;
|
|
106
144
|
}
|
|
107
145
|
|
|
108
146
|
static int
|
|
109
|
-
valid_xn_type(
|
|
147
|
+
valid_xn_type(mkr_xml_node_type_t type)
|
|
110
148
|
{
|
|
111
149
|
switch (type) {
|
|
112
150
|
case MKR_XML_NODE_TYPE_ELEMENT: case MKR_XML_NODE_TYPE_ATTRIBUTE: case MKR_XML_NODE_TYPE_TEXT:
|
|
@@ -120,7 +158,7 @@ valid_xn_type(uint8_t type)
|
|
|
120
158
|
}
|
|
121
159
|
|
|
122
160
|
mkr_xml_node_t *
|
|
123
|
-
mkr_xml_arena_node(mkr_xml_doc_t *doc,
|
|
161
|
+
mkr_xml_arena_node(mkr_xml_doc_t *doc, mkr_xml_node_type_t type)
|
|
124
162
|
{
|
|
125
163
|
if (doc == NULL) return NULL; /* fail-closed: never deref a NULL document */
|
|
126
164
|
/* guard against a caller passing a bogus type (programming error) so it can
|
|
@@ -149,11 +187,13 @@ mkr_xml_arena_bytes(mkr_xml_doc_t *doc, const char *src, uint32_t len)
|
|
|
149
187
|
return p;
|
|
150
188
|
}
|
|
151
189
|
|
|
152
|
-
/* A raw, uninitialised arena buffer of exactly +len+ bytes
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
|
|
156
|
-
|
|
190
|
+
/* A raw, uninitialised arena buffer of exactly +len+ bytes. PRIVATE: the only
|
|
191
|
+
* way to fill arena bytes by hand is mkr_xml_arena_spanbuf below, which wraps
|
|
192
|
+
* this in a core mkr_spanbuf (cursor + bounds check) so a caller can never
|
|
193
|
+
* overrun it. Budget-/
|
|
194
|
+
* overflow-checked via the same choke point. */
|
|
195
|
+
static char *
|
|
196
|
+
arena_scratch_bytes(mkr_xml_doc_t *doc, size_t len)
|
|
157
197
|
{
|
|
158
198
|
/* a 0-length buffer needs no storage - never cut a whole chunk for it. The
|
|
159
199
|
* sentinel is a valid non-NULL pointer the caller must not write to (there is
|
|
@@ -162,21 +202,113 @@ mkr_xml_arena_scratch_bytes(mkr_xml_doc_t *doc, size_t len)
|
|
|
162
202
|
return arena_alloc(doc, len);
|
|
163
203
|
}
|
|
164
204
|
|
|
205
|
+
/* Carve +cap+ arena bytes and wrap them in a core mkr_spanbuf (see
|
|
206
|
+
* mkr_xml_node.h). The spanbuf owns the cursor + bounds check, so no caller can
|
|
207
|
+
* overrun the cut; this is the sole sanctioned hand-fill path and the only thing
|
|
208
|
+
* that touches the private arena_scratch_bytes. On alloc failure the buffer is
|
|
209
|
+
* NULL, so the returned writer is already not-ok (every write a no-op). */
|
|
210
|
+
mkr_spanbuf_t
|
|
211
|
+
mkr_xml_arena_spanbuf(mkr_xml_doc_t *doc, size_t cap)
|
|
212
|
+
{
|
|
213
|
+
return mkr_spanbuf(arena_scratch_bytes(doc, cap), cap);
|
|
214
|
+
}
|
|
215
|
+
|
|
165
216
|
int
|
|
166
|
-
|
|
167
|
-
const char **uri, uint32_t *ulen)
|
|
217
|
+
mkr_xml_xmlns_prefix(const char *name, uint32_t len, const char **prefix, uint32_t *plen)
|
|
168
218
|
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
219
|
+
const char *p; uint32_t pl;
|
|
220
|
+
mkr_span_t s = mkr_span(name, len);
|
|
221
|
+
if (len == 5 && mkr_span_starts(&s, "xmlns", 5)) { /* read through the span - no raw name deref (NULL-safe) */
|
|
222
|
+
p = ""; pl = 0;
|
|
223
|
+
} else if (len > 6 && mkr_span_starts(&s, "xmlns:", 6)) {
|
|
224
|
+
p = name + 6; pl = len - 6;
|
|
173
225
|
} else {
|
|
174
226
|
return 0;
|
|
175
227
|
}
|
|
228
|
+
if (prefix != NULL) *prefix = p;
|
|
229
|
+
if (plen != NULL) *plen = pl;
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
int
|
|
234
|
+
mkr_xml_node_xmlns_decl(const mkr_xml_node_t *a, const char **prefix, uint32_t *plen,
|
|
235
|
+
const char **uri, uint32_t *ulen)
|
|
236
|
+
{
|
|
237
|
+
/* a built node's local IS qname+6 for "xmlns:p", so the byte detector's
|
|
238
|
+
* prefix slice equals the node's local - one shared rule, value read here. */
|
|
239
|
+
if (!mkr_xml_xmlns_prefix(a->qname, a->qname_len, prefix, plen)) return 0;
|
|
176
240
|
*uri = a->value ? a->value : ""; *ulen = a->value_len;
|
|
177
241
|
return 1;
|
|
178
242
|
}
|
|
179
243
|
|
|
244
|
+
mkr_xml_node_t *
|
|
245
|
+
mkr_xml_preorder_next(const mkr_xml_node_t *root, mkr_xml_node_t *cur)
|
|
246
|
+
{
|
|
247
|
+
if (cur->first_child != NULL) return cur->first_child;
|
|
248
|
+
/* climb until a node with a next sibling, stopping at (and not above) root */
|
|
249
|
+
while (cur != root && cur->next == NULL) cur = cur->parent;
|
|
250
|
+
if (cur == root) return NULL;
|
|
251
|
+
return cur->next;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
int
|
|
255
|
+
mkr_xml_qname_split(const char *name, uint32_t len, mkr_xml_qname_t *out)
|
|
256
|
+
{
|
|
257
|
+
if (len == 0) return -1;
|
|
258
|
+
mkr_span_t s = mkr_span(name, len);
|
|
259
|
+
uint32_t cp;
|
|
260
|
+
int bl = mkr_utf8_decode1_span(&s, &cp);
|
|
261
|
+
if (bl == 0 || !mkr_xml_is_name_start(cp)) return -1;
|
|
262
|
+
mkr_span_skip(&s, (size_t)bl);
|
|
263
|
+
while (mkr_span_left(&s) > 0) {
|
|
264
|
+
bl = mkr_utf8_decode1_span(&s, &cp);
|
|
265
|
+
if (bl == 0 || !mkr_xml_is_name_char(cp)) return -1;
|
|
266
|
+
mkr_span_skip(&s, (size_t)bl);
|
|
267
|
+
}
|
|
268
|
+
out->qname = name; out->qname_len = len;
|
|
269
|
+
mkr_span_t q = mkr_span(name, len);
|
|
270
|
+
size_t colon_at;
|
|
271
|
+
if (!mkr_span_find(&q, ':', &colon_at)) { /* no prefix */
|
|
272
|
+
out->prefix = name; out->prefix_len = 0;
|
|
273
|
+
out->local = name; out->local_len = len;
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
uint32_t pl = (uint32_t)colon_at;
|
|
277
|
+
const char *ls = name + colon_at + 1;
|
|
278
|
+
uint32_t ll = len - pl - 1;
|
|
279
|
+
if (pl == 0 || ll == 0) return -1; /* ":x" or "x:" */
|
|
280
|
+
mkr_span_t lsp = mkr_span(ls, ll);
|
|
281
|
+
size_t second;
|
|
282
|
+
if (mkr_span_find(&lsp, ':', &second)) return -1; /* a second colon */
|
|
283
|
+
if (mkr_utf8_decode1_span(&lsp, &cp) == 0 || !mkr_xml_is_name_start(cp))
|
|
284
|
+
return -1; /* local must be an NCName */
|
|
285
|
+
out->prefix = name; out->prefix_len = pl;
|
|
286
|
+
out->local = ls; out->local_len = ll;
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
int
|
|
291
|
+
mkr_xml_qname_assign(mkr_xml_doc_t *doc, mkr_xml_node_t *node, const mkr_xml_qname_t *qn)
|
|
292
|
+
{
|
|
293
|
+
/* Contract: qn->local must alias INTO [qn->qname, qn->qname+qname_len) - the
|
|
294
|
+
* rebase below adds (local - qname) onto the arena copy, so a non-aliasing
|
|
295
|
+
* local would mint an out-of-bounds slice (wrong-but-plausible data, the worst
|
|
296
|
+
* failure mode here). Both in-tree producers (split_qname in mkr_xml_tree.c,
|
|
297
|
+
* mkr_xml_qname_split above) honour it; pin it so a violation fails closed
|
|
298
|
+
* (INTERNAL, sticky) instead of silently passing. */
|
|
299
|
+
if (!(qn->local >= qn->qname && qn->local_len <= qn->qname_len
|
|
300
|
+
&& (size_t)(qn->local - qn->qname) <= qn->qname_len - qn->local_len)) {
|
|
301
|
+
if (doc != NULL) doc->oom = MKR_XML_ERR_INTERNAL;
|
|
302
|
+
return -1;
|
|
303
|
+
}
|
|
304
|
+
const char *q = mkr_xml_arena_bytes(doc, qn->qname, qn->qname_len);
|
|
305
|
+
if (qn->qname_len > 0 && q == NULL) return -1; /* OOM: node left untouched */
|
|
306
|
+
node->qname = q; node->qname_len = qn->qname_len;
|
|
307
|
+
node->local = q + (size_t)(qn->local - qn->qname); node->local_len = qn->local_len;
|
|
308
|
+
node->prefix = q; node->prefix_len = qn->prefix_len;
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
180
312
|
/* ---- self-test (Makiri.__c_selftest) - mirrors tmp/xml_spike/arena_spike.c --- */
|
|
181
313
|
int
|
|
182
314
|
mkr_xml_node_selftest(void)
|
|
@@ -191,7 +323,7 @@ mkr_xml_node_selftest(void)
|
|
|
191
323
|
char nm[] = "Feed";
|
|
192
324
|
const char *local = mkr_xml_arena_bytes(doc, nm, 4);
|
|
193
325
|
if (root == NULL || root->first_child != NULL || root->type != MKR_XML_NODE_TYPE_ELEMENT
|
|
194
|
-
|| local == NULL || local == nm ||
|
|
326
|
+
|| local == NULL || local == nm || !mkr_bytes_eq(local, 4, "Feed", 4)) {
|
|
195
327
|
mkr_xml_doc_destroy(doc); return idx;
|
|
196
328
|
}
|
|
197
329
|
/* pointer alignment */
|
|
@@ -256,7 +388,7 @@ mkr_xml_node_selftest(void)
|
|
|
256
388
|
idx++; /* 8 */
|
|
257
389
|
if (mkr_xml_arena_node(NULL, MKR_XML_NODE_TYPE_ELEMENT) != NULL
|
|
258
390
|
|| mkr_xml_arena_bytes(NULL, "x", 1) != NULL
|
|
259
|
-
||
|
|
391
|
+
|| mkr_xml_arena_spanbuf(NULL, 1).ok) { /* alloc fails -> not ok */
|
|
260
392
|
return idx;
|
|
261
393
|
}
|
|
262
394
|
|