Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <errno.h>
4 : #include <stddef.h>
5 : #include <string.h>
6 :
7 : #include "macro.h"
8 : #include "string-util.h"
9 : #include "xml.h"
10 :
11 : enum {
12 : STATE_NULL,
13 : STATE_TEXT,
14 : STATE_TAG,
15 : STATE_ATTRIBUTE,
16 : };
17 :
18 13 : static void inc_lines(unsigned *line, const char *s, size_t n) {
19 13 : const char *p = s;
20 :
21 13 : if (!line)
22 13 : return;
23 :
24 0 : for (;;) {
25 : const char *f;
26 :
27 0 : f = memchr(p, '\n', n);
28 0 : if (!f)
29 0 : return;
30 :
31 0 : n -= (f - p) + 1;
32 0 : p = f + 1;
33 0 : (*line)++;
34 : }
35 : }
36 :
37 : /* We don't actually do real XML here. We only read a simplistic
38 : * subset, that is a bit less strict that XML and lacks all the more
39 : * complex features, like entities, or namespaces. However, we do
40 : * support some HTML5-like simplifications */
41 :
42 17 : int xml_tokenize(const char **p, char **name, void **state, unsigned *line) {
43 : const char *c, *e, *b;
44 : char *ret;
45 : int t;
46 :
47 17 : assert(p);
48 17 : assert(*p);
49 17 : assert(name);
50 17 : assert(state);
51 :
52 17 : t = PTR_TO_INT(*state);
53 17 : c = *p;
54 :
55 17 : if (t == STATE_NULL) {
56 4 : if (line)
57 0 : *line = 1;
58 4 : t = STATE_TEXT;
59 : }
60 :
61 : for (;;) {
62 23 : if (*c == 0)
63 4 : return XML_END;
64 :
65 19 : switch (t) {
66 :
67 10 : case STATE_TEXT: {
68 : int x;
69 :
70 10 : e = strchrnul(c, '<');
71 10 : if (e > c) {
72 : /* More text... */
73 3 : ret = strndup(c, e - c);
74 3 : if (!ret)
75 0 : return -ENOMEM;
76 :
77 3 : inc_lines(line, c, e - c);
78 :
79 3 : *name = ret;
80 3 : *p = e;
81 3 : *state = INT_TO_PTR(STATE_TEXT);
82 :
83 3 : return XML_TEXT;
84 : }
85 :
86 7 : assert(*e == '<');
87 7 : b = c + 1;
88 :
89 7 : if (startswith(b, "!--")) {
90 : /* A comment */
91 1 : e = strstr(b + 3, "-->");
92 1 : if (!e)
93 0 : return -EINVAL;
94 :
95 1 : inc_lines(line, b, e + 3 - b);
96 :
97 1 : c = e + 3;
98 1 : continue;
99 : }
100 :
101 6 : if (*b == '?') {
102 : /* Processing instruction */
103 :
104 1 : e = strstr(b + 1, "?>");
105 1 : if (!e)
106 0 : return -EINVAL;
107 :
108 1 : inc_lines(line, b, e + 2 - b);
109 :
110 1 : c = e + 2;
111 1 : continue;
112 : }
113 :
114 5 : if (*b == '!') {
115 : /* DTD */
116 :
117 0 : e = strchr(b + 1, '>');
118 0 : if (!e)
119 0 : return -EINVAL;
120 :
121 0 : inc_lines(line, b, e + 1 - b);
122 :
123 0 : c = e + 1;
124 0 : continue;
125 : }
126 :
127 5 : if (*b == '/') {
128 : /* A closing tag */
129 2 : x = XML_TAG_CLOSE;
130 2 : b++;
131 : } else
132 3 : x = XML_TAG_OPEN;
133 :
134 5 : e = strpbrk(b, WHITESPACE "/>");
135 5 : if (!e)
136 0 : return -EINVAL;
137 :
138 5 : ret = strndup(b, e - b);
139 5 : if (!ret)
140 0 : return -ENOMEM;
141 :
142 5 : *name = ret;
143 5 : *p = e;
144 5 : *state = INT_TO_PTR(STATE_TAG);
145 :
146 5 : return x;
147 : }
148 :
149 7 : case STATE_TAG:
150 :
151 7 : b = c + strspn(c, WHITESPACE);
152 7 : if (*b == 0)
153 0 : return -EINVAL;
154 :
155 7 : inc_lines(line, c, b - c);
156 :
157 7 : e = b + strcspn(b, WHITESPACE "=/>");
158 7 : if (e > b) {
159 : /* An attribute */
160 :
161 2 : ret = strndup(b, e - b);
162 2 : if (!ret)
163 0 : return -ENOMEM;
164 :
165 2 : *name = ret;
166 2 : *p = e;
167 2 : *state = INT_TO_PTR(STATE_ATTRIBUTE);
168 :
169 2 : return XML_ATTRIBUTE_NAME;
170 : }
171 :
172 5 : if (startswith(b, "/>")) {
173 : /* An empty tag */
174 :
175 1 : *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */
176 1 : *p = b + 2;
177 1 : *state = INT_TO_PTR(STATE_TEXT);
178 :
179 1 : return XML_TAG_CLOSE_EMPTY;
180 : }
181 :
182 4 : if (*b != '>')
183 0 : return -EINVAL;
184 :
185 4 : c = b + 1;
186 4 : t = STATE_TEXT;
187 4 : continue;
188 :
189 2 : case STATE_ATTRIBUTE:
190 :
191 2 : if (*c == '=') {
192 2 : c++;
193 :
194 2 : if (IN_SET(*c, '\'', '"')) {
195 : /* Tag with a quoted value */
196 :
197 1 : e = strchr(c+1, *c);
198 1 : if (!e)
199 0 : return -EINVAL;
200 :
201 1 : inc_lines(line, c, e - c);
202 :
203 1 : ret = strndup(c+1, e - c - 1);
204 1 : if (!ret)
205 0 : return -ENOMEM;
206 :
207 1 : *name = ret;
208 1 : *p = e + 1;
209 1 : *state = INT_TO_PTR(STATE_TAG);
210 :
211 1 : return XML_ATTRIBUTE_VALUE;
212 :
213 : }
214 :
215 : /* Tag with a value without quotes */
216 :
217 1 : b = strpbrk(c, WHITESPACE ">");
218 1 : if (!b)
219 0 : b = c;
220 :
221 1 : ret = strndup(c, b - c);
222 1 : if (!ret)
223 0 : return -ENOMEM;
224 :
225 1 : *name = ret;
226 1 : *p = b;
227 1 : *state = INT_TO_PTR(STATE_TAG);
228 1 : return XML_ATTRIBUTE_VALUE;
229 : }
230 :
231 0 : t = STATE_TAG;
232 0 : continue;
233 : }
234 :
235 : }
236 :
237 : assert_not_reached("Bad state");
238 : }
|