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