LCOV - code coverage report
Current view: top level - src - parser_expat.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 153 172 89.0 %
Date: 2024-02-24 14:12:49 Functions: 13 15 86.7 %

          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          10 :         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             : }

Generated by: LCOV version 1.14