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
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
25 #include <libxml/parser.h>
27 #include "connection.h"
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
;
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
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"));
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"));
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
);
88 } else if(!xmlStrcmp(attributes
[i
], (xmlChar
*) "id")) {
89 g_free(js
->stream_id
);
90 js
->stream_id
= attrib
;
96 if (js
->stream_id
== NULL
) {
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"));
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");
118 node
= purple_xmlnode_new_child(js
->current
, (const char*) element_name
);
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];
140 int attrib_len
= attributes
[i
+4] - attributes
[i
+3];
141 char *attrib
= g_strndup((gchar
*)attributes
[i
+3], attrib_len
);
144 attrib
= purple_unescape_text(txt
);
146 purple_xmlnode_set_attrib_full(node
, name
, attrib_ns
, prefix
, attrib
);
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
;
163 if(js
->current
->parent
) {
164 if(!xmlStrcmp((xmlChar
*) js
->current
->name
, element_name
))
165 js
->current
= js
->current
->parent
;
167 PurpleXmlNode
*packet
= js
->current
;
169 jabber_process_packet(js
, &packet
);
171 purple_xmlnode_free(packet
);
176 jabber_parser_element_text_libxml(void *user_data
, const xmlChar
*text
, int text_len
)
178 JabberStream
*js
= user_data
;
183 if(!text
|| !text_len
)
186 purple_xmlnode_insert_data(js
->current
, (const char*) text
, text_len
);
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.
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)
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*/
225 NULL
, /*notationDecl*/
226 NULL
, /*attributeDecl*/
227 NULL
, /*elementDecl*/
228 NULL
, /*unparsedEntityDecl*/
229 NULL
, /*setDocumentLocator*/
230 NULL
, /*startDocument*/
231 NULL
, /*endDocument*/
232 NULL
, /*startElement*/
235 jabber_parser_element_text_libxml
, /*characters*/
236 NULL
, /*ignorableWhitespace*/
237 NULL
, /*processingInstruction*/
242 NULL
, /*getParameterEntity*/
244 NULL
, /*externalSubset*/
245 XML_SAX2_MAGIC
, /*initialized*/
247 jabber_parser_element_start_libxml
, /*startElementNs*/
248 jabber_parser_element_end_libxml
, /*endElementNs*/
249 jabber_parser_structured_error_handler
/*serror*/
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
) {
264 xmlParseChunk(js
->context
, NULL
,0,1);
265 xmlFreeParserCtxt(js
->context
);
270 void jabber_parser_process(JabberStream
*js
, const char *buf
, int len
)
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
;
293 purple_debug_info("jabber", "xmlParseChunk returned info %i\n", ret
);
295 case XML_ERR_WARNING
:
296 purple_debug_warning("jabber", "xmlParseChunk returned warning %i\n", ret
);
299 purple_debug_error("jabber", "xmlParseChunk returned error %i\n", ret
);
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"));
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
);