Branch data 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 : 52 : static void inc_lines(unsigned *line, const char *s, size_t n) {
19 : 52 : const char *p = s;
20 : :
21 [ + - ]: 52 : if (!line)
22 : 52 : 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 : 68 : 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 [ - + ]: 68 : assert(p);
48 [ - + ]: 68 : assert(*p);
49 [ - + ]: 68 : assert(name);
50 [ - + ]: 68 : assert(state);
51 : :
52 : 68 : t = PTR_TO_INT(*state);
53 : 68 : c = *p;
54 : :
55 [ + + ]: 68 : if (t == STATE_NULL) {
56 [ - + ]: 16 : if (line)
57 : 0 : *line = 1;
58 : 16 : t = STATE_TEXT;
59 : : }
60 : :
61 : : for (;;) {
62 [ + + ]: 92 : if (*c == 0)
63 : 16 : return XML_END;
64 : :
65 [ + + + - ]: 76 : switch (t) {
66 : :
67 : 40 : case STATE_TEXT: {
68 : : int x;
69 : :
70 : 40 : e = strchrnul(c, '<');
71 [ + + ]: 40 : if (e > c) {
72 : : /* More text... */
73 : 12 : ret = strndup(c, e - c);
74 [ - + ]: 12 : if (!ret)
75 : 0 : return -ENOMEM;
76 : :
77 : 12 : inc_lines(line, c, e - c);
78 : :
79 : 12 : *name = ret;
80 : 12 : *p = e;
81 : 12 : *state = INT_TO_PTR(STATE_TEXT);
82 : :
83 : 12 : return XML_TEXT;
84 : : }
85 : :
86 [ - + ]: 28 : assert(*e == '<');
87 : 28 : b = c + 1;
88 : :
89 [ + + ]: 28 : if (startswith(b, "!--")) {
90 : : /* A comment */
91 : 4 : e = strstr(b + 3, "-->");
92 [ - + ]: 4 : if (!e)
93 : 0 : return -EINVAL;
94 : :
95 : 4 : inc_lines(line, b, e + 3 - b);
96 : :
97 : 4 : c = e + 3;
98 : 4 : continue;
99 : : }
100 : :
101 [ + + ]: 24 : if (*b == '?') {
102 : : /* Processing instruction */
103 : :
104 : 4 : e = strstr(b + 1, "?>");
105 [ - + ]: 4 : if (!e)
106 : 0 : return -EINVAL;
107 : :
108 : 4 : inc_lines(line, b, e + 2 - b);
109 : :
110 : 4 : c = e + 2;
111 : 4 : continue;
112 : : }
113 : :
114 [ - + ]: 20 : 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 [ + + ]: 20 : if (*b == '/') {
128 : : /* A closing tag */
129 : 8 : x = XML_TAG_CLOSE;
130 : 8 : b++;
131 : : } else
132 : 12 : x = XML_TAG_OPEN;
133 : :
134 : 20 : e = strpbrk(b, WHITESPACE "/>");
135 [ - + ]: 20 : if (!e)
136 : 0 : return -EINVAL;
137 : :
138 : 20 : ret = strndup(b, e - b);
139 [ - + ]: 20 : if (!ret)
140 : 0 : return -ENOMEM;
141 : :
142 : 20 : *name = ret;
143 : 20 : *p = e;
144 : 20 : *state = INT_TO_PTR(STATE_TAG);
145 : :
146 : 20 : return x;
147 : : }
148 : :
149 : 28 : case STATE_TAG:
150 : :
151 : 28 : b = c + strspn(c, WHITESPACE);
152 [ - + ]: 28 : if (*b == 0)
153 : 0 : return -EINVAL;
154 : :
155 : 28 : inc_lines(line, c, b - c);
156 : :
157 : 28 : e = b + strcspn(b, WHITESPACE "=/>");
158 [ + + ]: 28 : if (e > b) {
159 : : /* An attribute */
160 : :
161 : 8 : ret = strndup(b, e - b);
162 [ - + ]: 8 : if (!ret)
163 : 0 : return -ENOMEM;
164 : :
165 : 8 : *name = ret;
166 : 8 : *p = e;
167 : 8 : *state = INT_TO_PTR(STATE_ATTRIBUTE);
168 : :
169 : 8 : return XML_ATTRIBUTE_NAME;
170 : : }
171 : :
172 [ + + ]: 20 : if (startswith(b, "/>")) {
173 : : /* An empty tag */
174 : :
175 : 4 : *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */
176 : 4 : *p = b + 2;
177 : 4 : *state = INT_TO_PTR(STATE_TEXT);
178 : :
179 : 4 : return XML_TAG_CLOSE_EMPTY;
180 : : }
181 : :
182 [ - + ]: 16 : if (*b != '>')
183 : 0 : return -EINVAL;
184 : :
185 : 16 : c = b + 1;
186 : 16 : t = STATE_TEXT;
187 : 16 : continue;
188 : :
189 : 8 : case STATE_ATTRIBUTE:
190 : :
191 [ + - ]: 8 : if (*c == '=') {
192 : 8 : c++;
193 : :
194 [ + + + + ]: 8 : if (IN_SET(*c, '\'', '"')) {
195 : : /* Tag with a quoted value */
196 : :
197 : 4 : e = strchr(c+1, *c);
198 [ - + ]: 4 : if (!e)
199 : 0 : return -EINVAL;
200 : :
201 : 4 : inc_lines(line, c, e - c);
202 : :
203 : 4 : ret = strndup(c+1, e - c - 1);
204 [ - + ]: 4 : if (!ret)
205 : 0 : return -ENOMEM;
206 : :
207 : 4 : *name = ret;
208 : 4 : *p = e + 1;
209 : 4 : *state = INT_TO_PTR(STATE_TAG);
210 : :
211 : 4 : return XML_ATTRIBUTE_VALUE;
212 : :
213 : : }
214 : :
215 : : /* Tag with a value without quotes */
216 : :
217 : 4 : b = strpbrk(c, WHITESPACE ">");
218 [ - + ]: 4 : if (!b)
219 : 0 : b = c;
220 : :
221 : 4 : ret = strndup(c, b - c);
222 [ - + ]: 4 : if (!ret)
223 : 0 : return -ENOMEM;
224 : :
225 : 4 : *name = ret;
226 : 4 : *p = b;
227 : 4 : *state = INT_TO_PTR(STATE_TAG);
228 : 4 : 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 : : }
|