Line data Source code
1 : /* SPDX-License-Identifier: MIT OR GPL-3.0-only */
2 : /* parser.c
3 : ** strophe XMPP client library -- xml parser handlers and utility functions
4 : **
5 : ** Copyright (C) 2005-2009 Collecta, Inc.
6 : **
7 : ** This software is provided AS-IS with no warranty, either express
8 : ** or implied.
9 : **
10 : ** This program is dual licensed under the MIT or GPLv3 licenses.
11 : */
12 :
13 : /** @file
14 : * XML parser handlers.
15 : */
16 :
17 : #include <stdio.h>
18 : #include <stdlib.h>
19 : #include <string.h>
20 :
21 : #include <expat.h>
22 :
23 : #include "strophe.h"
24 : #include "common.h"
25 : #include "parser.h"
26 :
27 : /* Allocate inner text by this number bytes more. Expat splits string
28 : * "new\nline" into 3 strings: "new" "\n" "line". Expecting this pattern,
29 : * we can leave few bytes in the inner_text for "\n". It should reduce
30 : * number of re-allocations in 2 times for multi-line texts. */
31 : #define INNER_TEXT_PADDING 2
32 :
33 : struct _parser_t {
34 : xmpp_ctx_t *ctx;
35 : XML_Parser expat;
36 : parser_start_callback startcb;
37 : parser_end_callback endcb;
38 : parser_stanza_callback stanzacb;
39 : void *userdata;
40 : int depth;
41 : xmpp_stanza_t *stanza;
42 : char *inner_text;
43 : /* number of allocated bytes */
44 : int inner_text_size;
45 : /* excluding terminal '\0' */
46 : int inner_text_used;
47 : };
48 :
49 : /* Use the Unit Separator to delimit namespace and name in our XML */
50 : const XML_Char namespace_sep = '\x1F';
51 :
52 : /*
53 : * Cached strophe ctx. It is used for memory suite.
54 : * Note, expat doesn't support userdata in memory suite, therefore,
55 : * we can support only one strophe context. If user creates more than one
56 : * context, this module will fallback to default library allocator for all
57 : * contexts other than mem_ctx.
58 : */
59 : static xmpp_ctx_t *mem_ctx = NULL;
60 :
61 193 : static void *parser_mem_malloc(size_t size)
62 : {
63 193 : if (mem_ctx != NULL)
64 193 : return strophe_alloc(mem_ctx, size);
65 : else
66 : return NULL;
67 : }
68 :
69 0 : static void *parser_mem_realloc(void *ptr, size_t size)
70 : {
71 0 : if (mem_ctx != NULL)
72 0 : return strophe_realloc(mem_ctx, ptr, size);
73 : else
74 : return NULL;
75 : }
76 :
77 1178 : static void parser_mem_free(void *ptr)
78 : {
79 1178 : if (mem_ctx != NULL)
80 1178 : strophe_free(mem_ctx, ptr);
81 1178 : }
82 :
83 : static const XML_Memory_Handling_Suite parser_mem_suite = {
84 : .malloc_fcn = &parser_mem_malloc,
85 : .realloc_fcn = &parser_mem_realloc,
86 : .free_fcn = &parser_mem_free,
87 : };
88 :
89 : /* return allocated string with the name from a delimited
90 : * namespace/name string */
91 48 : static char *_xml_name(xmpp_ctx_t *ctx, const char *nsname)
92 : {
93 48 : char *result = NULL;
94 48 : const char *c;
95 48 : size_t len;
96 :
97 48 : c = strchr(nsname, namespace_sep);
98 48 : if (c == NULL)
99 23 : return strophe_strdup(ctx, nsname);
100 :
101 25 : c++;
102 25 : len = strlen(c);
103 25 : result = strophe_alloc(ctx, len + 1);
104 25 : if (result != NULL) {
105 25 : memcpy(result, c, len);
106 25 : result[len] = '\0';
107 : }
108 :
109 : return result;
110 : }
111 :
112 : /* return allocated string with the namespace from a delimited string */
113 34 : static char *_xml_namespace(xmpp_ctx_t *ctx, const char *nsname)
114 : {
115 34 : char *result = NULL;
116 34 : const char *c;
117 :
118 34 : c = strchr(nsname, namespace_sep);
119 34 : if (c != NULL) {
120 25 : result = strophe_alloc(ctx, (c - nsname) + 1);
121 25 : if (result != NULL) {
122 25 : memcpy(result, nsname, (c - nsname));
123 25 : result[c - nsname] = '\0';
124 : }
125 : }
126 :
127 34 : return result;
128 : }
129 :
130 29 : static void _set_attributes(xmpp_stanza_t *stanza, const XML_Char **attrs)
131 : {
132 29 : char *attr;
133 29 : int i;
134 :
135 29 : if (!attrs)
136 : return;
137 :
138 43 : for (i = 0; attrs[i]; i += 2) {
139 : /* namespaced attributes aren't used in xmpp, discard namespace */
140 14 : attr = _xml_name(stanza->ctx, attrs[i]);
141 14 : xmpp_stanza_set_attribute(stanza, attr, attrs[i + 1]);
142 14 : strophe_free(stanza->ctx, attr);
143 : }
144 : }
145 :
146 50 : static void complete_inner_text(parser_t *parser)
147 : {
148 50 : xmpp_stanza_t *stanza;
149 :
150 50 : if (parser->inner_text) {
151 : /* create and populate stanza */
152 4 : stanza = xmpp_stanza_new(parser->ctx);
153 : /* FIXME: disconnect on allocation error */
154 4 : if (stanza) {
155 4 : xmpp_stanza_set_text(stanza, parser->inner_text);
156 4 : xmpp_stanza_add_child_ex(parser->stanza, stanza, 0);
157 : }
158 4 : strophe_free(parser->ctx, parser->inner_text);
159 4 : parser->inner_text = NULL;
160 4 : parser->inner_text_size = 0;
161 4 : parser->inner_text_used = 0;
162 : }
163 50 : }
164 :
165 : static void
166 34 : _start_element(void *userdata, const XML_Char *nsname, const XML_Char **attrs)
167 : {
168 34 : parser_t *parser = (parser_t *)userdata;
169 34 : xmpp_stanza_t *child;
170 34 : char *ns, *name;
171 :
172 34 : ns = _xml_namespace(parser->ctx, nsname);
173 34 : name = _xml_name(parser->ctx, nsname);
174 :
175 34 : if (parser->depth == 0) {
176 : /* notify the owner */
177 5 : if (parser->startcb)
178 5 : parser->startcb(name, (char **)attrs, parser->userdata);
179 : } else {
180 : /* build stanzas at depth 1 */
181 29 : if (!parser->stanza && parser->depth != 1) {
182 : /* something terrible happened */
183 : /* FIXME: shutdown disconnect */
184 0 : strophe_error(parser->ctx, "parser",
185 : "oops, where did our stanza go?");
186 : } else {
187 29 : child = xmpp_stanza_new(parser->ctx);
188 29 : if (!child) {
189 : /* FIXME: can't allocate, disconnect */
190 29 : }
191 29 : xmpp_stanza_set_name(child, name);
192 29 : _set_attributes(child, attrs);
193 29 : if (ns)
194 25 : xmpp_stanza_set_ns(child, ns);
195 :
196 29 : if (parser->stanza != NULL) {
197 23 : complete_inner_text(parser);
198 23 : xmpp_stanza_add_child_ex(parser->stanza, child, 0);
199 : }
200 29 : parser->stanza = child;
201 : }
202 : }
203 :
204 34 : if (ns)
205 25 : strophe_free(parser->ctx, ns);
206 34 : if (name)
207 34 : strophe_free(parser->ctx, name);
208 :
209 34 : parser->depth++;
210 34 : }
211 :
212 31 : static void _end_element(void *userdata, const XML_Char *name)
213 : {
214 31 : parser_t *parser = (parser_t *)userdata;
215 :
216 31 : parser->depth--;
217 :
218 31 : if (parser->depth == 0) {
219 : /* notify the owner */
220 4 : if (parser->endcb)
221 4 : parser->endcb((char *)name, parser->userdata);
222 : } else {
223 27 : complete_inner_text(parser);
224 27 : if (parser->stanza->parent) {
225 : /* we're finishing a child stanza, so set current to the parent */
226 22 : parser->stanza = parser->stanza->parent;
227 : } else {
228 5 : if (parser->stanzacb)
229 5 : parser->stanzacb(parser->stanza, parser->userdata);
230 5 : xmpp_stanza_release(parser->stanza);
231 5 : parser->stanza = NULL;
232 : }
233 : }
234 31 : }
235 :
236 5 : static void _characters(void *userdata, const XML_Char *s, int len)
237 : {
238 5 : parser_t *parser = (parser_t *)userdata;
239 5 : char *p;
240 :
241 5 : if (parser->depth < 2)
242 : return;
243 :
244 : /* Join all parts to a single resulting string. Stanza is created in
245 : * _start_element() and _end_element(). */
246 5 : if (parser->inner_text_used + len >= parser->inner_text_size) {
247 5 : parser->inner_text_size =
248 5 : parser->inner_text_used + len + 1 + INNER_TEXT_PADDING;
249 5 : p = strophe_realloc(parser->ctx, parser->inner_text,
250 : parser->inner_text_size);
251 5 : if (p == NULL) {
252 0 : strophe_free(parser->ctx, parser->inner_text);
253 0 : parser->inner_text = NULL;
254 0 : parser->inner_text_used = 0;
255 0 : parser->inner_text_size = 0;
256 0 : return;
257 : }
258 5 : parser->inner_text = p;
259 5 : parser->inner_text[parser->inner_text_used] = '\0';
260 : }
261 5 : parser->inner_text_used += len;
262 5 : strncat(parser->inner_text, s, len);
263 : }
264 :
265 13 : parser_t *parser_new(xmpp_ctx_t *ctx,
266 : parser_start_callback startcb,
267 : parser_end_callback endcb,
268 : parser_stanza_callback stanzacb,
269 : void *userdata)
270 : {
271 13 : parser_t *parser;
272 :
273 13 : parser = strophe_alloc(ctx, sizeof(parser_t));
274 13 : if (parser != NULL) {
275 13 : parser->ctx = ctx;
276 13 : parser->expat = NULL;
277 13 : parser->startcb = startcb;
278 13 : parser->endcb = endcb;
279 13 : parser->stanzacb = stanzacb;
280 13 : parser->userdata = userdata;
281 13 : parser->depth = 0;
282 13 : parser->stanza = NULL;
283 13 : parser->inner_text = NULL;
284 13 : parser->inner_text_size = 0;
285 13 : parser->inner_text_used = 0;
286 :
287 13 : parser_reset(parser);
288 : }
289 :
290 13 : return parser;
291 : }
292 :
293 0 : char *parser_attr_name(xmpp_ctx_t *ctx, char *nsname)
294 : {
295 0 : return _xml_name(ctx, nsname);
296 : }
297 :
298 : static void _free_parent_stanza(xmpp_stanza_t *stanza)
299 : {
300 : xmpp_stanza_t *parent;
301 :
302 2 : for (parent = stanza; parent->parent != NULL; parent = parent->parent)
303 : ;
304 1 : xmpp_stanza_release(parent);
305 : }
306 :
307 : /* free a parser */
308 13 : void parser_free(parser_t *parser)
309 : {
310 13 : if (parser->expat)
311 13 : XML_ParserFree(parser->expat);
312 :
313 13 : if (parser->stanza) {
314 1 : _free_parent_stanza(parser->stanza);
315 1 : parser->stanza = NULL;
316 : }
317 :
318 13 : if (parser->inner_text) {
319 1 : strophe_free(parser->ctx, parser->inner_text);
320 1 : parser->inner_text = NULL;
321 : }
322 :
323 13 : strophe_free(parser->ctx, parser);
324 13 : }
325 :
326 : /* shuts down and restarts XML parser. true on success */
327 13 : int parser_reset(parser_t *parser)
328 : {
329 13 : XML_Bool ret;
330 13 : const XML_Memory_Handling_Suite *mem = NULL;
331 :
332 13 : if (parser->expat) {
333 0 : ret = XML_ParserReset(parser->expat, NULL);
334 0 : if (ret != XML_TRUE) {
335 0 : XML_ParserFree(parser->expat);
336 0 : parser->expat = NULL;
337 : }
338 : } else {
339 13 : if (mem_ctx == NULL)
340 2 : mem_ctx = parser->ctx;
341 13 : if (parser->ctx == mem_ctx)
342 13 : mem = &parser_mem_suite;
343 13 : parser->expat = XML_ParserCreate_MM(NULL, mem, &namespace_sep);
344 : }
345 :
346 13 : if (parser->stanza) {
347 0 : _free_parent_stanza(parser->stanza);
348 0 : parser->stanza = NULL;
349 : }
350 :
351 13 : if (parser->inner_text) {
352 0 : strophe_free(parser->ctx, parser->inner_text);
353 0 : parser->inner_text = NULL;
354 : }
355 :
356 13 : if (!parser->expat)
357 : return 0;
358 :
359 13 : parser->depth = 0;
360 :
361 13 : XML_SetUserData(parser->expat, parser);
362 13 : XML_SetElementHandler(parser->expat, _start_element, _end_element);
363 13 : XML_SetCharacterDataHandler(parser->expat, _characters);
364 :
365 13 : return 1;
366 : }
367 :
368 15 : int parser_feed(parser_t *parser, char *chunk, int len)
369 : {
370 15 : return XML_Parse(parser->expat, chunk, len, 0);
371 : }
|