/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 | | |