Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / parser.c
bloba7e69039f42dbce0667c4beeaaffa3b3e5602dae
1 /*
2 * purple - Jabber XML parser stuff
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.
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.
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.
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
23 #include "internal.h"
25 #include <libxml/parser.h>
27 #include "connection.h"
28 #include "debug.h"
29 #include "jabber.h"
30 #include "parser.h"
31 #include "util.h"
32 #include "xmlnode.h"
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)
40 JabberStream *js = user_data;
41 PurpleXmlNode *node;
42 int i, j;
44 if(!element_name) {
45 return;
46 } else if (js->stream_id == NULL) {
47 /* Sanity checking! */
48 if (0 != xmlStrcmp(element_name, (xmlChar *) "stream") ||
49 0 != xmlStrcmp(namespace, (xmlChar *) NS_XMPP_STREAMS)) {
50 /* We were expecting a <stream:stream/> opening stanza, but
51 * didn't get it. Bad!
53 purple_debug_error("jabber", "Expecting stream header, got %s with "
54 "xmlns %s\n", element_name, namespace);
55 purple_connection_error(js->gc,
56 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
57 _("XMPP stream header missing"));
58 return;
61 js->protocol_version.major = 0;
62 js->protocol_version.minor = 9;
64 for (i = 0; i < nb_attributes * 5; i += 5) {
65 int attrib_len = attributes[i+4] - attributes[i+3];
66 char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len);
68 if(!xmlStrcmp(attributes[i], (xmlChar*) "version")) {
69 const char *dot = strchr(attrib, '.');
71 js->protocol_version.major = atoi(attrib);
72 js->protocol_version.minor = dot ? atoi(dot + 1) : 0;
74 if (js->protocol_version.major > 1) {
75 /* TODO: Send <unsupported-version/> error */
76 purple_connection_error(js->gc,
77 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
78 _("XMPP Version Mismatch"));
79 g_free(attrib);
80 return;
83 if (js->protocol_version.major == 0 && js->protocol_version.minor != 9) {
84 purple_debug_warning("jabber", "Treating version %s as 0.9 for backward "
85 "compatibility\n", attrib);
87 g_free(attrib);
88 } else if(!xmlStrcmp(attributes[i], (xmlChar*) "id")) {
89 g_free(js->stream_id);
90 js->stream_id = attrib;
91 } else {
92 g_free(attrib);
96 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.
101 purple_connection_error(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.
109 js->stream_id = g_strdup("");
110 purple_debug_info("jabber", "Server failed to specify a stream "
111 "ID (underspecified in rfc3920, but intended "
112 "to be a MUST; digest legacy auth may fail.\n");
113 #endif
115 } else {
117 if(js->current)
118 node = purple_xmlnode_new_child(js->current, (const char*) element_name);
119 else
120 node = purple_xmlnode_new((const char*) element_name);
121 purple_xmlnode_set_namespace(node, (const char*) namespace);
122 purple_xmlnode_set_prefix(node, (const char *)prefix);
124 if (nb_namespaces != 0) {
125 node->namespace_map = g_hash_table_new_full(
126 g_str_hash, g_str_equal, g_free, g_free);
128 for (i = 0, j = 0; i < nb_namespaces; i++, j += 2) {
129 const char *key = (const char *)namespaces[j];
130 const char *val = (const char *)namespaces[j + 1];
131 g_hash_table_insert(node->namespace_map,
132 g_strdup(key ? key : ""), g_strdup(val ? val : ""));
135 for(i=0; i < nb_attributes * 5; i+=5) {
136 const char *name = (const char *)attributes[i];
137 const char *prefix = (const char *)attributes[i+1];
138 const char *attrib_ns = (const char *)attributes[i+2];
139 char *txt;
140 int attrib_len = attributes[i+4] - attributes[i+3];
141 char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len);
143 txt = attrib;
144 attrib = purple_unescape_text(txt);
145 g_free(txt);
146 purple_xmlnode_set_attrib_full(node, name, attrib_ns, prefix, attrib);
147 g_free(attrib);
150 js->current = node;
154 static void
155 jabber_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
156 const xmlChar *prefix, const xmlChar *namespace)
158 JabberStream *js = user_data;
160 if(!js->current)
161 return;
163 if(js->current->parent) {
164 if(!xmlStrcmp((xmlChar*) js->current->name, element_name))
165 js->current = js->current->parent;
166 } else {
167 PurpleXmlNode *packet = js->current;
168 js->current = NULL;
169 jabber_process_packet(js, &packet);
170 if (packet != NULL)
171 purple_xmlnode_free(packet);
175 static void
176 jabber_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
178 JabberStream *js = user_data;
180 if(!js->current)
181 return;
183 if(!text || !text_len)
184 return;
186 purple_xmlnode_insert_data(js->current, (const char*) text, text_len);
189 static void
190 jabber_parser_structured_error_handler(void *user_data, xmlErrorPtr error)
192 JabberStream *js = user_data;
194 if (error->level == XML_ERR_WARNING && error->message != NULL
195 && g_str_equal(error->message, "xmlns: URI vcard-temp is not absolute\n"))
197 * This message happens when parsing vcards, and is normal, so don't
198 * bother logging it because people scare easily.
200 return;
202 if (error->level == XML_ERR_FATAL && error->code == XML_ERR_DOCUMENT_END)
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)
208 return;
210 purple_debug_error("jabber", "XML parser error for JabberStream %p: "
211 "Domain %i, code %i, level %i: %s",
213 error->domain, error->code, error->level,
214 (error->message ? error->message : "(null)\n"));
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*/
252 void
253 jabber_parser_setup(JabberStream *js)
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 jabber_parser_free(js);
262 void jabber_parser_free(JabberStream *js) {
263 if (js->context) {
264 xmlParseChunk(js->context, NULL,0,1);
265 xmlFreeParserCtxt(js->context);
266 js->context = NULL;
270 void jabber_parser_process(JabberStream *js, const char *buf, int len)
272 int ret;
274 if (js->context == NULL) {
275 /* libxml inconsistently starts parsing on creating the
276 * parser, so do a ParseChunk right afterwards to force it. */
277 js->context = xmlCreatePushParserCtxt(&jabber_parser_libxml, js, buf, len, NULL);
278 xmlParseChunk(js->context, "", 0, 0);
279 } else if ((ret = xmlParseChunk(js->context, buf, len, 0)) != XML_ERR_OK) {
280 xmlError *err = xmlCtxtGetLastError(js->context);
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.
286 xmlErrorLevel level = XML_ERR_WARNING;
288 if (err)
289 level = err->level;
291 switch (level) {
292 case XML_ERR_NONE:
293 purple_debug_info("jabber", "xmlParseChunk returned info %i\n", ret);
294 break;
295 case XML_ERR_WARNING:
296 purple_debug_warning("jabber", "xmlParseChunk returned warning %i\n", ret);
297 break;
298 case XML_ERR_ERROR:
299 purple_debug_error("jabber", "xmlParseChunk returned error %i\n", ret);
300 break;
301 case XML_ERR_FATAL:
302 purple_debug_error("jabber", "xmlParseChunk returned fatal %i\n", ret);
303 purple_connection_error (js->gc,
304 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
305 _("XML Parse error"));
306 break;
310 if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
311 !purple_connection_get_error_info(js->gc) &&
312 (js->state == JABBER_STREAM_INITIALIZING ||
313 js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)) {
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.
319 jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
320 jabber_auth_start_old(js);