| 1 /* |
|
| 2 * gaim - Jabber Protocol Plugin |
|
| 3 * |
|
| 4 * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> |
|
| 5 * |
|
| 6 * This program is free software; you can redistribute it and/or modify |
|
| 7 * it under the terms of the GNU General Public License as published by |
|
| 8 * the Free Software Foundation; either version 2 of the License, or |
|
| 9 * (at your option) any later version. |
|
| 10 * |
|
| 11 * This program is distributed in the hope that it will be useful, |
|
| 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 * GNU General Public License for more details. |
|
| 15 * |
|
| 16 * You should have received a copy of the GNU General Public License |
|
| 17 * along with this program; if not, write to the Free Software |
|
| 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
| 19 * |
|
| 20 */ |
|
| 21 |
|
| 22 #include "internal.h" |
|
| 23 #include "prefs.h" |
|
| 24 |
|
| 25 #include "buddy.h" |
|
| 26 #include "iq.h" |
|
| 27 #include "disco.h" |
|
| 28 #include "jabber.h" |
|
| 29 |
|
| 30 |
|
| 31 struct _jabber_disco_info_cb_data { |
|
| 32 gpointer data; |
|
| 33 JabberDiscoInfoCallback *callback; |
|
| 34 }; |
|
| 35 |
|
| 36 #define SUPPORT_FEATURE(x) \ |
|
| 37 feature = xmlnode_new_child(query, "feature"); \ |
|
| 38 xmlnode_set_attrib(feature, "var", x); |
|
| 39 |
|
| 40 |
|
| 41 void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { |
|
| 42 const char *from = xmlnode_get_attrib(packet, "from"); |
|
| 43 const char *type = xmlnode_get_attrib(packet, "type"); |
|
| 44 |
|
| 45 if(!from || !type) |
|
| 46 return; |
|
| 47 |
|
| 48 if(!strcmp(type, "get")) { |
|
| 49 xmlnode *query, *identity, *feature; |
|
| 50 JabberIq *iq; |
|
| 51 |
|
| 52 xmlnode *in_query; |
|
| 53 const char *node = NULL; |
|
| 54 |
|
| 55 if((in_query = xmlnode_get_child(packet, "query"))) { |
|
| 56 node = xmlnode_get_attrib(in_query, "node"); |
|
| 57 } |
|
| 58 |
|
| 59 |
|
| 60 iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, |
|
| 61 "http://jabber.org/protocol/disco#info"); |
|
| 62 |
|
| 63 jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); |
|
| 64 |
|
| 65 xmlnode_set_attrib(iq->node, "to", from); |
|
| 66 query = xmlnode_get_child(iq->node, "query"); |
|
| 67 |
|
| 68 if(node) |
|
| 69 xmlnode_set_attrib(query, "node", node); |
|
| 70 |
|
| 71 if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) { |
|
| 72 |
|
| 73 identity = xmlnode_new_child(query, "identity"); |
|
| 74 xmlnode_set_attrib(identity, "category", "client"); |
|
| 75 xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console, |
|
| 76 * handheld, pc, phone, |
|
| 77 * web */ |
|
| 78 xmlnode_set_attrib(identity, "name", PACKAGE); |
|
| 79 |
|
| 80 SUPPORT_FEATURE("jabber:iq:last") |
|
| 81 SUPPORT_FEATURE("jabber:iq:oob") |
|
| 82 SUPPORT_FEATURE("jabber:iq:time") |
|
| 83 SUPPORT_FEATURE("jabber:iq:version") |
|
| 84 SUPPORT_FEATURE("jabber:x:conference") |
|
| 85 SUPPORT_FEATURE("http://jabber.org/protocol/bytestreams") |
|
| 86 SUPPORT_FEATURE("http://jabber.org/protocol/disco#info") |
|
| 87 SUPPORT_FEATURE("http://jabber.org/protocol/disco#items") |
|
| 88 #if 0 |
|
| 89 SUPPORT_FEATURE("http://jabber.org/protocol/ibb") |
|
| 90 #endif |
|
| 91 SUPPORT_FEATURE("http://jabber.org/protocol/muc") |
|
| 92 SUPPORT_FEATURE("http://jabber.org/protocol/muc#user") |
|
| 93 SUPPORT_FEATURE("http://jabber.org/protocol/si") |
|
| 94 SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer") |
|
| 95 SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im") |
|
| 96 } else { |
|
| 97 xmlnode *error, *inf; |
|
| 98 |
|
| 99 /* XXX: gross hack, implement jabber_iq_set_type or something */ |
|
| 100 xmlnode_set_attrib(iq->node, "type", "error"); |
|
| 101 iq->type = JABBER_IQ_ERROR; |
|
| 102 |
|
| 103 error = xmlnode_new_child(query, "error"); |
|
| 104 xmlnode_set_attrib(error, "code", "404"); |
|
| 105 xmlnode_set_attrib(error, "type", "cancel"); |
|
| 106 inf = xmlnode_new_child(error, "item-not-found"); |
|
| 107 xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); |
|
| 108 } |
|
| 109 |
|
| 110 jabber_iq_send(iq); |
|
| 111 } else if(!strcmp(type, "result")) { |
|
| 112 xmlnode *query = xmlnode_get_child(packet, "query"); |
|
| 113 xmlnode *child; |
|
| 114 JabberID *jid; |
|
| 115 JabberBuddy *jb; |
|
| 116 JabberBuddyResource *jbr = NULL; |
|
| 117 JabberCapabilities capabilities = JABBER_CAP_NONE; |
|
| 118 struct _jabber_disco_info_cb_data *jdicd; |
|
| 119 |
|
| 120 if((jid = jabber_id_new(from))) { |
|
| 121 if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) |
|
| 122 jbr = jabber_buddy_find_resource(jb, jid->resource); |
|
| 123 jabber_id_free(jid); |
|
| 124 } |
|
| 125 |
|
| 126 if(jbr) |
|
| 127 capabilities = jbr->capabilities; |
|
| 128 |
|
| 129 for(child = query->child; child; child = child->next) { |
|
| 130 if(child->type != XMLNODE_TYPE_TAG) |
|
| 131 continue; |
|
| 132 |
|
| 133 if(!strcmp(child->name, "identity")) { |
|
| 134 const char *category = xmlnode_get_attrib(child, "category"); |
|
| 135 const char *type = xmlnode_get_attrib(child, "type"); |
|
| 136 if(!category || !type) |
|
| 137 continue; |
|
| 138 |
|
| 139 if(!strcmp(category, "conference") && !strcmp(type, "text")) { |
|
| 140 /* we found a groupchat or MUC server, add it to the list */ |
|
| 141 /* XXX: actually check for protocol/muc or gc-1.0 support */ |
|
| 142 js->chat_servers = g_list_append(js->chat_servers, g_strdup(from)); |
|
| 143 } else if(!strcmp(category, "directory") && !strcmp(type, "user")) { |
|
| 144 /* we found a JUD */ |
|
| 145 js->user_directories = g_list_append(js->user_directories, g_strdup(from)); |
|
| 146 } |
|
| 147 |
|
| 148 } else if(!strcmp(child->name, "feature")) { |
|
| 149 const char *var = xmlnode_get_attrib(child, "var"); |
|
| 150 if(!var) |
|
| 151 continue; |
|
| 152 |
|
| 153 if(!strcmp(var, "http://jabber.org/protocol/si")) |
|
| 154 capabilities |= JABBER_CAP_SI; |
|
| 155 else if(!strcmp(var, "http://jabber.org/protocol/si/profile/file-transfer")) |
|
| 156 capabilities |= JABBER_CAP_SI_FILE_XFER; |
|
| 157 else if(!strcmp(var, "http://jabber.org/protocol/bytestreams")) |
|
| 158 capabilities |= JABBER_CAP_BYTESTREAMS; |
|
| 159 else if(!strcmp(var, "jabber:iq:search")) |
|
| 160 capabilities |= JABBER_CAP_IQ_SEARCH; |
|
| 161 else if(!strcmp(var, "jabber:iq:register")) |
|
| 162 capabilities |= JABBER_CAP_IQ_REGISTER; |
|
| 163 } |
|
| 164 } |
|
| 165 |
|
| 166 capabilities |= JABBER_CAP_RETRIEVED; |
|
| 167 |
|
| 168 if(jbr) |
|
| 169 jbr->capabilities = capabilities; |
|
| 170 |
|
| 171 if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) { |
|
| 172 jdicd->callback(js, from, capabilities, jdicd->data); |
|
| 173 g_hash_table_remove(js->disco_callbacks, from); |
|
| 174 } |
|
| 175 } else if(!strcmp(type, "error")) { |
|
| 176 JabberID *jid; |
|
| 177 JabberBuddy *jb; |
|
| 178 JabberBuddyResource *jbr = NULL; |
|
| 179 JabberCapabilities capabilities = JABBER_CAP_NONE; |
|
| 180 struct _jabber_disco_info_cb_data *jdicd; |
|
| 181 |
|
| 182 if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from))) |
|
| 183 return; |
|
| 184 |
|
| 185 if((jid = jabber_id_new(from))) { |
|
| 186 if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) |
|
| 187 jbr = jabber_buddy_find_resource(jb, jid->resource); |
|
| 188 jabber_id_free(jid); |
|
| 189 } |
|
| 190 |
|
| 191 if(jbr) |
|
| 192 capabilities = jbr->capabilities; |
|
| 193 |
|
| 194 jdicd->callback(js, from, capabilities, jdicd->data); |
|
| 195 g_hash_table_remove(js->disco_callbacks, from); |
|
| 196 } |
|
| 197 } |
|
| 198 |
|
| 199 void jabber_disco_items_parse(JabberStream *js, xmlnode *packet) { |
|
| 200 const char *from = xmlnode_get_attrib(packet, "from"); |
|
| 201 const char *type = xmlnode_get_attrib(packet, "type"); |
|
| 202 |
|
| 203 if(!strcmp(type, "get")) { |
|
| 204 JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, |
|
| 205 "http://jabber.org/protocol/disco#items"); |
|
| 206 |
|
| 207 jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); |
|
| 208 |
|
| 209 xmlnode_set_attrib(iq->node, "to", from); |
|
| 210 jabber_iq_send(iq); |
|
| 211 } |
|
| 212 } |
|
| 213 |
|
| 214 static void |
|
| 215 jabber_disco_server_result_cb(JabberStream *js, xmlnode *packet, gpointer data) |
|
| 216 { |
|
| 217 xmlnode *query, *child; |
|
| 218 const char *from = xmlnode_get_attrib(packet, "from"); |
|
| 219 const char *type = xmlnode_get_attrib(packet, "type"); |
|
| 220 |
|
| 221 if(!from || !type) |
|
| 222 return; |
|
| 223 |
|
| 224 if(strcmp(from, js->user->domain)) |
|
| 225 return; |
|
| 226 |
|
| 227 if(strcmp(type, "result")) |
|
| 228 return; |
|
| 229 |
|
| 230 while(js->chat_servers) { |
|
| 231 g_free(js->chat_servers->data); |
|
| 232 js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers); |
|
| 233 } |
|
| 234 |
|
| 235 query = xmlnode_get_child(packet, "query"); |
|
| 236 |
|
| 237 for(child = xmlnode_get_child(query, "item"); child; |
|
| 238 child = xmlnode_get_next_twin(child)) { |
|
| 239 JabberIq *iq; |
|
| 240 const char *jid; |
|
| 241 |
|
| 242 if(!(jid = xmlnode_get_attrib(child, "jid"))) |
|
| 243 continue; |
|
| 244 |
|
| 245 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); |
|
| 246 xmlnode_set_attrib(iq->node, "to", jid); |
|
| 247 jabber_iq_send(iq); |
|
| 248 } |
|
| 249 } |
|
| 250 |
|
| 251 void jabber_disco_items_server(JabberStream *js) |
|
| 252 { |
|
| 253 JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, |
|
| 254 "http://jabber.org/protocol/disco#items"); |
|
| 255 |
|
| 256 xmlnode_set_attrib(iq->node, "to", js->user->domain); |
|
| 257 |
|
| 258 jabber_iq_set_callback(iq, jabber_disco_server_result_cb, NULL); |
|
| 259 jabber_iq_send(iq); |
|
| 260 } |
|
| 261 |
|
| 262 void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data) |
|
| 263 { |
|
| 264 JabberID *jid; |
|
| 265 JabberBuddy *jb; |
|
| 266 JabberBuddyResource *jbr = NULL; |
|
| 267 struct _jabber_disco_info_cb_data *jdicd; |
|
| 268 JabberIq *iq; |
|
| 269 |
|
| 270 if((jid = jabber_id_new(who))) { |
|
| 271 if(jid->resource && (jb = jabber_buddy_find(js, who, TRUE))) |
|
| 272 jbr = jabber_buddy_find_resource(jb, jid->resource); |
|
| 273 jabber_id_free(jid); |
|
| 274 } |
|
| 275 |
|
| 276 if(jbr && jbr->capabilities & JABBER_CAP_RETRIEVED) { |
|
| 277 callback(js, who, jbr->capabilities, data); |
|
| 278 return; |
|
| 279 } |
|
| 280 |
|
| 281 jdicd = g_new0(struct _jabber_disco_info_cb_data, 1); |
|
| 282 jdicd->data = data; |
|
| 283 jdicd->callback = callback; |
|
| 284 |
|
| 285 g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd); |
|
| 286 |
|
| 287 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); |
|
| 288 xmlnode_set_attrib(iq->node, "to", who); |
|
| 289 |
|
| 290 jabber_iq_send(iq); |
|
| 291 } |
|
| 292 |
|
| 293 |
|