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
) {
97 /* Let's make up a placeholder stream ID, which we need to do
98 * because we flag on it being NULL as a special case in this
101 js
->stream_id
= g_strdup("");
102 purple_debug_info("jabber", "Server failed to specify a stream "
103 "ID (underspecified in rfc3920, but intended "
104 "to be a MUST; digest legacy auth may fail.\n");
109 node
= purple_xmlnode_new_child(js
->current
, (const char*) element_name
);
111 node
= purple_xmlnode_new((const char*) element_name
);
112 purple_xmlnode_set_namespace(node
, (const char*) namespace);
113 purple_xmlnode_set_prefix(node
, (const char *)prefix
);
115 if (nb_namespaces
!= 0) {
116 node
->namespace_map
= g_hash_table_new_full(
117 g_str_hash
, g_str_equal
, g_free
, g_free
);
119 for (i
= 0, j
= 0; i
< nb_namespaces
; i
++, j
+= 2) {
120 const char *key
= (const char *)namespaces
[j
];
121 const char *val
= (const char *)namespaces
[j
+ 1];
122 g_hash_table_insert(node
->namespace_map
,
123 g_strdup(key
? key
: ""), g_strdup(val
? val
: ""));
126 for(i
=0; i
< nb_attributes
* 5; i
+=5) {
127 const char *name
= (const char *)attributes
[i
];
128 const char *prefix
= (const char *)attributes
[i
+1];
129 const char *attrib_ns
= (const char *)attributes
[i
+2];
131 int attrib_len
= attributes
[i
+4] - attributes
[i
+3];
132 char *attrib
= g_strndup((gchar
*)attributes
[i
+3], attrib_len
);
135 attrib
= purple_unescape_text(txt
);
137 purple_xmlnode_set_attrib_full(node
, name
, attrib_ns
, prefix
, attrib
);
146 jabber_parser_element_end_libxml(void *user_data
, const xmlChar
*element_name
,
147 const xmlChar
*prefix
, const xmlChar
*namespace)
149 JabberStream
*js
= user_data
;
154 if(js
->current
->parent
) {
155 if(!xmlStrcmp((xmlChar
*) js
->current
->name
, element_name
))
156 js
->current
= js
->current
->parent
;
158 PurpleXmlNode
*packet
= js
->current
;
160 jabber_process_packet(js
, &packet
);
162 purple_xmlnode_free(packet
);
167 jabber_parser_element_text_libxml(void *user_data
, const xmlChar
*text
, int text_len
)
169 JabberStream
*js
= user_data
;
174 if(!text
|| !text_len
)
177 purple_xmlnode_insert_data(js
->current
, (const char*) text
, text_len
);
181 jabber_parser_structured_error_handler(void *user_data
, xmlErrorPtr error
)
183 JabberStream
*js
= user_data
;
185 if (error
->level
== XML_ERR_WARNING
186 && purple_strequal(error
->message
, "xmlns: URI vcard-temp is not absolute\n"))
188 * This message happens when parsing vcards, and is normal, so don't
189 * bother logging it because people scare easily.
193 if (error
->level
== XML_ERR_FATAL
&& error
->code
== XML_ERR_DOCUMENT_END
)
195 * This is probably more annoying than the vcard-temp error; it occurs
196 * because we disconnect in most cases without waiting for the receiving
197 * </stream:stream> (limitations of libpurple)
201 purple_debug_error("jabber", "XML parser error for JabberStream %p: "
202 "Domain %i, code %i, level %i: %s",
204 error
->domain
, error
->code
, error
->level
,
205 (error
->message
? error
->message
: "(null)\n"));
208 static xmlSAXHandler jabber_parser_libxml
= {
209 NULL
, /*internalSubset*/
210 NULL
, /*isStandalone*/
211 NULL
, /*hasInternalSubset*/
212 NULL
, /*hasExternalSubset*/
213 NULL
, /*resolveEntity*/
216 NULL
, /*notationDecl*/
217 NULL
, /*attributeDecl*/
218 NULL
, /*elementDecl*/
219 NULL
, /*unparsedEntityDecl*/
220 NULL
, /*setDocumentLocator*/
221 NULL
, /*startDocument*/
222 NULL
, /*endDocument*/
223 NULL
, /*startElement*/
226 jabber_parser_element_text_libxml
, /*characters*/
227 NULL
, /*ignorableWhitespace*/
228 NULL
, /*processingInstruction*/
233 NULL
, /*getParameterEntity*/
235 NULL
, /*externalSubset*/
236 XML_SAX2_MAGIC
, /*initialized*/
238 jabber_parser_element_start_libxml
, /*startElementNs*/
239 jabber_parser_element_end_libxml
, /*endElementNs*/
240 jabber_parser_structured_error_handler
/*serror*/
244 jabber_parser_setup(JabberStream
*js
)
246 /* This seems backwards, but it makes sense. The libxml code creates
247 * the parser context when you try to use it (this way, it can figure
248 * out the encoding at creation time. So, setting up the parser is
249 * just a matter of destroying any current parser. */
250 jabber_parser_free(js
);
253 void jabber_parser_free(JabberStream
*js
) {
255 xmlParseChunk(js
->context
, NULL
,0,1);
256 xmlFreeParserCtxt(js
->context
);
261 void jabber_parser_process(JabberStream
*js
, const char *buf
, int len
)
265 if (js
->context
== NULL
) {
266 /* libxml inconsistently starts parsing on creating the
267 * parser, so do a ParseChunk right afterwards to force it. */
268 js
->context
= xmlCreatePushParserCtxt(&jabber_parser_libxml
, js
, buf
, len
, NULL
);
269 xmlParseChunk(js
->context
, "", 0, 0);
270 } else if ((ret
= xmlParseChunk(js
->context
, buf
, len
, 0)) != XML_ERR_OK
) {
271 xmlError
*err
= xmlCtxtGetLastError(js
->context
);
273 * libxml2 uses a global setting to determine whether or not to store
274 * warnings. Other libraries may set this, which causes err to be
275 * NULL. See #8136 for details.
277 xmlErrorLevel level
= XML_ERR_WARNING
;
284 purple_debug_info("jabber", "xmlParseChunk returned info %i\n", ret
);
286 case XML_ERR_WARNING
:
287 purple_debug_warning("jabber", "xmlParseChunk returned warning %i\n", ret
);
290 purple_debug_error("jabber", "xmlParseChunk returned error %i\n", ret
);
293 purple_debug_error("jabber", "xmlParseChunk returned fatal %i\n", ret
);
294 purple_connection_error (js
->gc
,
295 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
296 _("XML Parse error"));
301 if (js
->protocol_version
.major
== 0 && js
->protocol_version
.minor
== 9 &&
302 !purple_connection_get_error_info(js
->gc
) &&
303 (js
->state
== JABBER_STREAM_INITIALIZING
||
304 js
->state
== JABBER_STREAM_INITIALIZING_ENCRYPTION
)) {
306 * Legacy servers don't advertise features, so if we've just gotten
307 * the opening <stream:stream> and there was no version, we need to
308 * immediately start legacy IQ auth.
310 jabber_stream_set_state(js
, JABBER_STREAM_AUTHENTICATING
);
311 jabber_auth_start_old(js
);