rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / parser.c
blob9c3ef54baed524cd1139785fd82014e2a6815e6d
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 /* 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
99 * parsing code.
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");
106 } else {
108 if(js->current)
109 node = purple_xmlnode_new_child(js->current, (const char*) element_name);
110 else
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];
130 char *txt;
131 int attrib_len = attributes[i+4] - attributes[i+3];
132 char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len);
134 txt = attrib;
135 attrib = purple_unescape_text(txt);
136 g_free(txt);
137 purple_xmlnode_set_attrib_full(node, name, attrib_ns, prefix, attrib);
138 g_free(attrib);
141 js->current = node;
145 static void
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;
151 if(!js->current)
152 return;
154 if(js->current->parent) {
155 if(!xmlStrcmp((xmlChar*) js->current->name, element_name))
156 js->current = js->current->parent;
157 } else {
158 PurpleXmlNode *packet = js->current;
159 js->current = NULL;
160 jabber_process_packet(js, &packet);
161 if (packet != NULL)
162 purple_xmlnode_free(packet);
166 static void
167 jabber_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
169 JabberStream *js = user_data;
171 if(!js->current)
172 return;
174 if(!text || !text_len)
175 return;
177 purple_xmlnode_insert_data(js->current, (const char*) text, text_len);
180 static void
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.
191 return;
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)
199 return;
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*/
214 NULL, /*getEntity*/
215 NULL, /*entityDecl*/
216 NULL, /*notationDecl*/
217 NULL, /*attributeDecl*/
218 NULL, /*elementDecl*/
219 NULL, /*unparsedEntityDecl*/
220 NULL, /*setDocumentLocator*/
221 NULL, /*startDocument*/
222 NULL, /*endDocument*/
223 NULL, /*startElement*/
224 NULL, /*endElement*/
225 NULL, /*reference*/
226 jabber_parser_element_text_libxml, /*characters*/
227 NULL, /*ignorableWhitespace*/
228 NULL, /*processingInstruction*/
229 NULL, /*comment*/
230 NULL, /*warning*/
231 NULL, /*error*/
232 NULL, /*fatalError*/
233 NULL, /*getParameterEntity*/
234 NULL, /*cdataBlock*/
235 NULL, /*externalSubset*/
236 XML_SAX2_MAGIC, /*initialized*/
237 NULL, /*_private*/
238 jabber_parser_element_start_libxml, /*startElementNs*/
239 jabber_parser_element_end_libxml, /*endElementNs*/
240 jabber_parser_structured_error_handler /*serror*/
243 void
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) {
254 if (js->context) {
255 xmlParseChunk(js->context, NULL,0,1);
256 xmlFreeParserCtxt(js->context);
257 js->context = NULL;
261 void jabber_parser_process(JabberStream *js, const char *buf, int len)
263 int ret;
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;
279 if (err)
280 level = err->level;
282 switch (level) {
283 case XML_ERR_NONE:
284 purple_debug_info("jabber", "xmlParseChunk returned info %i\n", ret);
285 break;
286 case XML_ERR_WARNING:
287 purple_debug_warning("jabber", "xmlParseChunk returned warning %i\n", ret);
288 break;
289 case XML_ERR_ERROR:
290 purple_debug_error("jabber", "xmlParseChunk returned error %i\n", ret);
291 break;
292 case XML_ERR_FATAL:
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"));
297 break;
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);