Coverage Report

Created: 2026-10-28 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/parser.c
Line
Count
Source
1
/*
2
 * purple - Jabber XML parser stuff
3
 *
4
 * Purple is the legal property of its developers, whose names are too numerous
5
 * to list here.  Please refer to the COPYRIGHT file distributed with this
6
 * source distribution.
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
#include "internal.h"
24
25
#include <libxml/parser.h>
26
27
#include "connection.h"
28
#include "debug.h"
29
#include "jabber.h"
30
#include "parser.h"
31
#include "util.h"
32
#include "xmlnode.h"
33
34
static void
35
jabber_parser_element_start_libxml(void *user_data,
36
           const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace,
37
           int nb_namespaces, const xmlChar **namespaces,
38
           int nb_attributes, int nb_defaulted, const xmlChar **attributes)
39
0
{
40
0
  JabberStream *js = user_data;
41
0
  xmlnode *node;
42
0
  int i, j;
43
44
0
  if(!element_name) {
45
0
    return;
46
0
  } else if (js->stream_id == NULL) {
47
    /* Sanity checking! */
48
0
    if (0 != xmlStrcmp(element_name, (xmlChar *) "stream") ||
49
0
        0 != xmlStrcmp(namespace, (xmlChar *) NS_XMPP_STREAMS)) {
50
      /* We were expecting a <stream:stream/> opening stanza, but
51
       * didn't get it. Bad!
52
       */
53
0
      purple_debug_error("jabber", "Expecting stream header, got %s with "
54
0
                         "xmlns %s\n", element_name, namespace);
55
0
      purple_connection_error_reason(js->gc,
56
0
          PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
57
0
          _("XMPP stream header missing"));
58
0
      return;
59
0
    }
60
61
0
    js->protocol_version.major = 0;
62
0
    js->protocol_version.minor = 9;
63
64
0
    for (i = 0; i < nb_attributes * 5; i += 5) {
65
0
      int attrib_len = attributes[i+4] - attributes[i+3];
66
0
      char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len);
67
68
0
      if(!xmlStrcmp(attributes[i], (xmlChar*) "version")) {
69
0
        const char *dot = strchr(attrib, '.');
70
71
0
        js->protocol_version.major = atoi(attrib);
72
0
        js->protocol_version.minor = dot ? atoi(dot + 1) : 0;
73
74
0
        if (js->protocol_version.major > 1) {
75
          /* TODO: Send <unsupported-version/> error */
76
0
          purple_connection_error_reason(js->gc,
77
0
              PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
78
0
              _("XMPP Version Mismatch"));
79
0
          g_free(attrib);
80
0
          return;
81
0
        }
82
83
0
        if (js->protocol_version.major == 0 && js->protocol_version.minor != 9) {
84
0
          purple_debug_warning("jabber", "Treating version %s as 0.9 for backward "
85
0
                               "compatibility\n", attrib);
86
0
        }
87
0
        g_free(attrib);
88
0
      } else if(!xmlStrcmp(attributes[i], (xmlChar*) "id")) {
89
0
        g_free(js->stream_id);
90
0
        js->stream_id = attrib;
91
0
      } else {
92
0
        g_free(attrib);
93
0
      }
94
0
    }
95
96
0
    if (js->stream_id == NULL) {
97
#if 0
98
      /* This was underspecified in rfc3920 as only being a SHOULD, so
99
       * we cannot rely on it.  See #12331 and Oracle's server.
100
       */
101
      purple_connection_error_reason(js->gc,
102
          PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
103
          _("XMPP stream missing ID"));
104
#else
105
      /* Instead, let's make up a placeholder stream ID, which we need
106
       * to do because we flag on it being NULL as a special case
107
       * in this parsing code.
108
       */
109
0
      js->stream_id = g_strdup("");
110
0
      purple_debug_info("jabber", "Server failed to specify a stream "
111
0
                        "ID (underspecified in rfc3920, but intended "
112
0
                        "to be a MUST; digest legacy auth may fail.\n");
113
0
#endif
114
0
    }
115
0
  } else {
116
117
0
    if(js->current)
118
0
      node = xmlnode_new_child(js->current, (const char*) element_name);
119
0
    else
120
0
      node = xmlnode_new((const char*) element_name);
121
0
    xmlnode_set_namespace(node, (const char*) namespace);
122
0
    xmlnode_set_prefix(node, (const char *)prefix);
123
124
0
    if (nb_namespaces != 0) {
125
0
      node->namespace_map = g_hash_table_new_full(
126
0
        g_str_hash, g_str_equal, g_free, g_free);
127
128
0
      for (i = 0, j = 0; i < nb_namespaces; i++, j += 2) {
129
0
        const char *key = (const char *)namespaces[j];
130
0
        const char *val = (const char *)namespaces[j + 1];
131
0
        g_hash_table_insert(node->namespace_map,
132
0
          g_strdup(key ? key : ""), g_strdup(val ? val : ""));
133
0
      }
134
0
    }
135
0
    for(i=0; i < nb_attributes * 5; i+=5) {
136
0
      const char *name = (const char *)attributes[i];
137
0
      const char *prefix = (const char *)attributes[i+1];
138
0
      const char *attrib_ns = (const char *)attributes[i+2];
139
0
      char *txt;
140
0
      int attrib_len = attributes[i+4] - attributes[i+3];
141
0
      char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len);
142
143
0
      txt = attrib;
144
0
      attrib = purple_unescape_text(txt);
145
0
      g_free(txt);
146
0
      xmlnode_set_attrib_full(node, name, attrib_ns, prefix, attrib);
147
0
      g_free(attrib);
148
0
    }
149
150
0
    js->current = node;
151
0
  }
152
0
}
153
154
static void
155
jabber_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
156
         const xmlChar *prefix, const xmlChar *namespace)
157
0
{
158
0
  JabberStream *js = user_data;
159
160
0
  if(!js->current)
161
0
    return;
162
163
0
  if(js->current->parent) {
164
0
    if(!xmlStrcmp((xmlChar*) js->current->name, element_name))
165
0
      js->current = js->current->parent;
166
0
  } else {
167
0
    xmlnode *packet = js->current;
168
0
    js->current = NULL;
169
0
    jabber_process_packet(js, &packet);
170
0
    if (packet != NULL)
171
0
      xmlnode_free(packet);
172
0
  }
173
0
}
174
175
static void
176
jabber_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
177
0
{
178
0
  JabberStream *js = user_data;
179
180
0
  if(!js->current)
181
0
    return;
182
183
0
  if(!text || !text_len)
184
0
    return;
185
186
0
  xmlnode_insert_data(js->current, (const char*) text, text_len);
187
0
}
188
189
static void
190
jabber_parser_structured_error_handler(void *user_data, xmlErrorPtr error)
191
0
{
192
0
  JabberStream *js = user_data;
193
194
0
  if (error->level == XML_ERR_WARNING
195
0
      && purple_strequal(error->message, "xmlns: URI vcard-temp is not absolute\n"))
196
    /*
197
     * This message happens when parsing vcards, and is normal, so don't
198
     * bother logging it because people scare easily.
199
     */
200
0
    return;
201
202
0
  if (error->level == XML_ERR_FATAL && error->code == XML_ERR_DOCUMENT_END)
203
    /*
204
     * This is probably more annoying than the vcard-temp error; it occurs
205
     * because we disconnect in most cases without waiting for the receiving
206
     * </stream:stream> (limitations of libpurple)
207
     */
208
0
    return;
209
210
0
  purple_debug_error("jabber", "XML parser error for JabberStream %p: "
211
0
                 "Domain %i, code %i, level %i: %s",
212
0
             js,
213
0
             error->domain, error->code, error->level,
214
0
             (error->message ? error->message : "(null)\n"));
215
0
}
216
217
static xmlSAXHandler jabber_parser_libxml = {
218
  NULL,                 /*internalSubset*/
219
  NULL,                 /*isStandalone*/
220
  NULL,                 /*hasInternalSubset*/
221
  NULL,                 /*hasExternalSubset*/
222
  NULL,                 /*resolveEntity*/
223
  NULL,                 /*getEntity*/
224
  NULL,                 /*entityDecl*/
225
  NULL,                 /*notationDecl*/
226
  NULL,                 /*attributeDecl*/
227
  NULL,                 /*elementDecl*/
228
  NULL,                 /*unparsedEntityDecl*/
229
  NULL,                 /*setDocumentLocator*/
230
  NULL,                 /*startDocument*/
231
  NULL,                 /*endDocument*/
232
  NULL,                 /*startElement*/
233
  NULL,                 /*endElement*/
234
  NULL,                 /*reference*/
235
  jabber_parser_element_text_libxml,    /*characters*/
236
  NULL,                 /*ignorableWhitespace*/
237
  NULL,                 /*processingInstruction*/
238
  NULL,                 /*comment*/
239
  NULL,                 /*warning*/
240
  NULL,                 /*error*/
241
  NULL,                 /*fatalError*/
242
  NULL,                 /*getParameterEntity*/
243
  NULL,                 /*cdataBlock*/
244
  NULL,                 /*externalSubset*/
245
  XML_SAX2_MAGIC,             /*initialized*/
246
  NULL,                 /*_private*/
247
  jabber_parser_element_start_libxml,   /*startElementNs*/
248
  jabber_parser_element_end_libxml,   /*endElementNs*/
249
  jabber_parser_structured_error_handler  /*serror*/
250
};
251
252
void
253
jabber_parser_setup(JabberStream *js)
254
0
{
255
  /* This seems backwards, but it makes sense. The libxml code creates
256
   * the parser context when you try to use it (this way, it can figure
257
   * out the encoding at creation time. So, setting up the parser is
258
   * just a matter of destroying any current parser. */
259
0
  jabber_parser_free(js);
260
0
}
261
262
0
void jabber_parser_free(JabberStream *js) {
263
0
  if (js->context) {
264
0
    xmlParseChunk(js->context, NULL,0,1);
265
0
    xmlFreeParserCtxt(js->context);
266
0
    js->context = NULL;
267
0
  }
268
0
}
269
270
void jabber_parser_process(JabberStream *js, const char *buf, int len)
271
0
{
272
0
  int ret;
273
274
0
  if (js->context == NULL) {
275
    /* libxml inconsistently starts parsing on creating the
276
     * parser, so do a ParseChunk right afterwards to force it. */
277
0
    js->context = xmlCreatePushParserCtxt(&jabber_parser_libxml, js, buf, len, NULL);
278
0
    xmlParseChunk(js->context, "", 0, 0);
279
0
  } else if ((ret = xmlParseChunk(js->context, buf, len, 0)) != XML_ERR_OK) {
280
0
    xmlError *err = xmlCtxtGetLastError(js->context);
281
    /*
282
     * libxml2 uses a global setting to determine whether or not to store
283
     * warnings.  Other libraries may set this, which causes err to be
284
     * NULL. See #8136 for details.
285
     */
286
0
    xmlErrorLevel level = XML_ERR_WARNING;
287
288
0
    if (err)
289
0
      level = err->level;
290
291
0
    switch (level) {
292
0
      case XML_ERR_NONE:
293
0
        purple_debug_info("jabber", "xmlParseChunk returned info %i\n", ret);
294
0
        break;
295
0
      case XML_ERR_WARNING:
296
0
        purple_debug_warning("jabber", "xmlParseChunk returned warning %i\n", ret);
297
0
        break;
298
0
      case XML_ERR_ERROR:
299
0
        purple_debug_error("jabber", "xmlParseChunk returned error %i\n", ret);
300
0
        break;
301
0
      case XML_ERR_FATAL:
302
0
        purple_debug_error("jabber", "xmlParseChunk returned fatal %i\n", ret);
303
0
        purple_connection_error_reason (js->gc,
304
0
                                        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
305
0
                                        _("XML Parse error"));
306
0
        break;
307
0
    }
308
0
  }
309
310
0
  if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
311
0
      !js->gc->disconnect_timeout &&
312
0
      (js->state == JABBER_STREAM_INITIALIZING ||
313
0
       js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)) {
314
    /*
315
     * Legacy servers don't advertise features, so if we've just gotten
316
     * the opening <stream:stream> and there was no version, we need to
317
     * immediately start legacy IQ auth.
318
     */
319
0
    jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
320
0
    jabber_auth_start_old(js);
321
0
  }
322
0
}
323