| |
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 #include "internal.h" |
| |
22 #include "debug.h" |
| |
23 #include "server.h" |
| |
24 #include "util.h" |
| |
25 |
| |
26 #include "buddy.h" |
| |
27 #include "google.h" |
| |
28 #include "presence.h" |
| |
29 #include "roster.h" |
| |
30 #include "iq.h" |
| |
31 |
| |
32 #include <string.h> |
| |
33 |
| |
34 |
| |
35 void jabber_roster_request(JabberStream *js) |
| |
36 { |
| |
37 JabberIq *iq; |
| |
38 |
| |
39 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); |
| |
40 |
| |
41 jabber_iq_send(iq); |
| |
42 } |
| |
43 |
| |
44 static void remove_gaim_buddies(JabberStream *js, const char *jid) |
| |
45 { |
| |
46 GSList *buddies, *l; |
| |
47 |
| |
48 buddies = gaim_find_buddies(js->gc->account, jid); |
| |
49 |
| |
50 for(l = buddies; l; l = l->next) |
| |
51 gaim_blist_remove_buddy(l->data); |
| |
52 |
| |
53 g_slist_free(buddies); |
| |
54 } |
| |
55 |
| |
56 static void add_gaim_buddies_in_groups(JabberStream *js, const char *jid, |
| |
57 const char *alias, GSList *groups) |
| |
58 { |
| |
59 GSList *buddies, *g2, *l; |
| |
60 gchar *my_bare_jid; |
| |
61 |
| |
62 buddies = gaim_find_buddies(js->gc->account, jid); |
| |
63 |
| |
64 g2 = groups; |
| |
65 |
| |
66 if(!groups) { |
| |
67 if(!buddies) |
| |
68 g2 = g_slist_append(g2, g_strdup(_("Buddies"))); |
| |
69 else |
| |
70 return; |
| |
71 } |
| |
72 |
| |
73 my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); |
| |
74 |
| |
75 while(buddies) { |
| |
76 GaimBuddy *b = buddies->data; |
| |
77 GaimGroup *g = gaim_buddy_get_group(b); |
| |
78 |
| |
79 buddies = g_slist_remove(buddies, b); |
| |
80 |
| |
81 if((l = g_slist_find_custom(g2, g->name, (GCompareFunc)strcmp))) { |
| |
82 const char *servernick; |
| |
83 |
| |
84 if((servernick = gaim_blist_node_get_string((GaimBlistNode*)b, "servernick"))) |
| |
85 serv_got_alias(js->gc, jid, servernick); |
| |
86 |
| |
87 if(alias && (!b->alias || strcmp(b->alias, alias))) |
| |
88 gaim_blist_alias_buddy(b, alias); |
| |
89 g_free(l->data); |
| |
90 g2 = g_slist_delete_link(g2, l); |
| |
91 } else { |
| |
92 gaim_blist_remove_buddy(b); |
| |
93 } |
| |
94 } |
| |
95 |
| |
96 while(g2) { |
| |
97 GaimBuddy *b = gaim_buddy_new(js->gc->account, jid, alias); |
| |
98 GaimGroup *g = gaim_find_group(g2->data); |
| |
99 |
| |
100 if(!g) { |
| |
101 g = gaim_group_new(g2->data); |
| |
102 gaim_blist_add_group(g, NULL); |
| |
103 } |
| |
104 |
| |
105 gaim_blist_add_buddy(b, NULL, g, NULL); |
| |
106 gaim_blist_alias_buddy(b, alias); |
| |
107 |
| |
108 /* If we just learned about ourself, then fake our status, |
| |
109 * because we won't be receiving a normal presence message |
| |
110 * about ourself. */ |
| |
111 if(!strcmp(b->name, my_bare_jid)) { |
| |
112 GaimPresence *gpresence; |
| |
113 GaimStatus *status; |
| |
114 |
| |
115 gpresence = gaim_account_get_presence(js->gc->account); |
| |
116 status = gaim_presence_get_active_status(gpresence); |
| |
117 jabber_presence_fake_to_self(js, status); |
| |
118 } |
| |
119 |
| |
120 g_free(g2->data); |
| |
121 g2 = g_slist_delete_link(g2, g2); |
| |
122 } |
| |
123 |
| |
124 g_free(my_bare_jid); |
| |
125 g_slist_free(buddies); |
| |
126 } |
| |
127 |
| |
128 void jabber_roster_parse(JabberStream *js, xmlnode *packet) |
| |
129 { |
| |
130 xmlnode *query, *item, *group; |
| |
131 const char *from = xmlnode_get_attrib(packet, "from"); |
| |
132 |
| |
133 if(from) { |
| |
134 char *from_norm; |
| |
135 gboolean invalid; |
| |
136 |
| |
137 from_norm = g_strdup(jabber_normalize(js->gc->account, from)); |
| |
138 |
| |
139 if(!from_norm) |
| |
140 return; |
| |
141 |
| |
142 invalid = g_utf8_collate(from_norm, |
| |
143 jabber_normalize(js->gc->account, |
| |
144 gaim_account_get_username(js->gc->account))); |
| |
145 |
| |
146 g_free(from_norm); |
| |
147 |
| |
148 if(invalid) |
| |
149 return; |
| |
150 } |
| |
151 |
| |
152 query = xmlnode_get_child(packet, "query"); |
| |
153 if(!query) |
| |
154 return; |
| |
155 |
| |
156 js->roster_parsed = TRUE; |
| |
157 |
| |
158 for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) |
| |
159 { |
| |
160 const char *jid, *name, *subscription, *ask; |
| |
161 JabberBuddy *jb; |
| |
162 |
| |
163 subscription = xmlnode_get_attrib(item, "subscription"); |
| |
164 jid = xmlnode_get_attrib(item, "jid"); |
| |
165 name = xmlnode_get_attrib(item, "name"); |
| |
166 ask = xmlnode_get_attrib(item, "ask"); |
| |
167 |
| |
168 if(!jid) |
| |
169 continue; |
| |
170 |
| |
171 if(!(jb = jabber_buddy_find(js, jid, TRUE))) |
| |
172 continue; |
| |
173 |
| |
174 if(subscription) { |
| |
175 gint me = -1; |
| |
176 char *jid_norm; |
| |
177 const char *username; |
| |
178 |
| |
179 jid_norm = g_strdup(jabber_normalize(js->gc->account, jid)); |
| |
180 username = gaim_account_get_username(js->gc->account); |
| |
181 me = g_utf8_collate(jid_norm, |
| |
182 jabber_normalize(js->gc->account, |
| |
183 username)); |
| |
184 |
| |
185 if(me == 0) |
| |
186 jb->subscription = JABBER_SUB_BOTH; |
| |
187 else if(!strcmp(subscription, "none")) |
| |
188 jb->subscription = JABBER_SUB_NONE; |
| |
189 else if(!strcmp(subscription, "to")) |
| |
190 jb->subscription = JABBER_SUB_TO; |
| |
191 else if(!strcmp(subscription, "from")) |
| |
192 jb->subscription = JABBER_SUB_FROM; |
| |
193 else if(!strcmp(subscription, "both")) |
| |
194 jb->subscription = JABBER_SUB_BOTH; |
| |
195 else if(!strcmp(subscription, "remove")) |
| |
196 jb->subscription = JABBER_SUB_REMOVE; |
| |
197 /* XXX: if subscription is now "from" or "none" we need to |
| |
198 * fake a signoff, since we won't get any presence from them |
| |
199 * anymore */ |
| |
200 /* YYY: I was going to use this, but I'm not sure it's necessary |
| |
201 * anymore, but it's here in case it is. */ |
| |
202 /* |
| |
203 if ((jb->subscription & JABBER_SUB_FROM) || |
| |
204 (jb->subscription & JABBER_SUB_NONE)) { |
| |
205 gaim_prpl_got_user_status(js->gc->account, jid, "offline", NULL); |
| |
206 } |
| |
207 */ |
| |
208 } |
| |
209 |
| |
210 if(ask && !strcmp(ask, "subscribe")) |
| |
211 jb->subscription |= JABBER_SUB_PENDING; |
| |
212 else |
| |
213 jb->subscription &= ~JABBER_SUB_PENDING; |
| |
214 |
| |
215 if(jb->subscription == JABBER_SUB_REMOVE) { |
| |
216 remove_gaim_buddies(js, jid); |
| |
217 } else { |
| |
218 GSList *groups = NULL; |
| |
219 for(group = xmlnode_get_child(item, "group"); group; group = xmlnode_get_next_twin(group)) { |
| |
220 char *group_name; |
| |
221 |
| |
222 if(!(group_name = xmlnode_get_data(group))) |
| |
223 group_name = g_strdup(""); |
| |
224 |
| |
225 if (g_slist_find_custom(groups, group_name, (GCompareFunc)gaim_utf8_strcasecmp) == NULL) |
| |
226 groups = g_slist_append(groups, group_name); |
| |
227 } |
| |
228 if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) |
| |
229 jabber_google_roster_incoming(js, item); |
| |
230 add_gaim_buddies_in_groups(js, jid, name, groups); |
| |
231 } |
| |
232 } |
| |
233 } |
| |
234 |
| |
235 static void jabber_roster_update(JabberStream *js, const char *name, |
| |
236 GSList *grps) |
| |
237 { |
| |
238 GaimBuddy *b; |
| |
239 GaimGroup *g; |
| |
240 GSList *groups = NULL, *l; |
| |
241 JabberIq *iq; |
| |
242 xmlnode *query, *item, *group; |
| |
243 |
| |
244 if(grps) { |
| |
245 groups = grps; |
| |
246 } else { |
| |
247 GSList *buddies = gaim_find_buddies(js->gc->account, name); |
| |
248 if(!buddies) |
| |
249 return; |
| |
250 while(buddies) { |
| |
251 b = buddies->data; |
| |
252 g = gaim_buddy_get_group(b); |
| |
253 groups = g_slist_append(groups, g->name); |
| |
254 buddies = g_slist_remove(buddies, b); |
| |
255 } |
| |
256 } |
| |
257 |
| |
258 if(!(b = gaim_find_buddy(js->gc->account, name))) |
| |
259 return; |
| |
260 |
| |
261 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); |
| |
262 |
| |
263 query = xmlnode_get_child(iq->node, "query"); |
| |
264 item = xmlnode_new_child(query, "item"); |
| |
265 |
| |
266 xmlnode_set_attrib(item, "jid", name); |
| |
267 |
| |
268 xmlnode_set_attrib(item, "name", b->alias ? b->alias : ""); |
| |
269 |
| |
270 for(l = groups; l; l = l->next) { |
| |
271 group = xmlnode_new_child(item, "group"); |
| |
272 xmlnode_insert_data(group, l->data, -1); |
| |
273 } |
| |
274 |
| |
275 if(!grps) |
| |
276 g_slist_free(groups); |
| |
277 |
| |
278 if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { |
| |
279 jabber_google_roster_outgoing(js, query, item); |
| |
280 xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); |
| |
281 xmlnode_set_attrib(query, "gr:ext", "2"); |
| |
282 } |
| |
283 jabber_iq_send(iq); |
| |
284 } |
| |
285 |
| |
286 void jabber_roster_add_buddy(GaimConnection *gc, GaimBuddy *buddy, |
| |
287 GaimGroup *group) |
| |
288 { |
| |
289 JabberStream *js = gc->proto_data; |
| |
290 char *who; |
| |
291 GSList *groups = NULL; |
| |
292 JabberBuddy *jb; |
| |
293 JabberBuddyResource *jbr; |
| |
294 char *my_bare_jid; |
| |
295 |
| |
296 if(!js->roster_parsed) |
| |
297 return; |
| |
298 |
| |
299 if(!(who = jabber_get_bare_jid(buddy->name))) |
| |
300 return; |
| |
301 |
| |
302 jb = jabber_buddy_find(js, buddy->name, FALSE); |
| |
303 |
| |
304 if(!jb || !(jb->subscription & JABBER_SUB_TO)) { |
| |
305 groups = g_slist_append(groups, group->name); |
| |
306 } |
| |
307 |
| |
308 jabber_roster_update(js, who, groups); |
| |
309 |
| |
310 my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); |
| |
311 if(!strcmp(who, my_bare_jid)) { |
| |
312 GaimPresence *gpresence; |
| |
313 GaimStatus *status; |
| |
314 |
| |
315 gpresence = gaim_account_get_presence(js->gc->account); |
| |
316 status = gaim_presence_get_active_status(gpresence); |
| |
317 jabber_presence_fake_to_self(js, status); |
| |
318 } else if(!jb || !(jb->subscription & JABBER_SUB_TO)) { |
| |
319 jabber_presence_subscription_set(js, who, "subscribe"); |
| |
320 } else if((jbr =jabber_buddy_find_resource(jb, NULL))) { |
| |
321 gaim_prpl_got_user_status(gc->account, who, |
| |
322 jabber_buddy_state_get_status_id(jbr->state), |
| |
323 "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL); |
| |
324 } |
| |
325 |
| |
326 g_free(my_bare_jid); |
| |
327 g_free(who); |
| |
328 } |
| |
329 |
| |
330 void jabber_roster_alias_change(GaimConnection *gc, const char *name, const char *alias) |
| |
331 { |
| |
332 GaimBuddy *b = gaim_find_buddy(gc->account, name); |
| |
333 |
| |
334 if(b != NULL) { |
| |
335 gaim_blist_alias_buddy(b, alias); |
| |
336 |
| |
337 jabber_roster_update(gc->proto_data, name, NULL); |
| |
338 } |
| |
339 } |
| |
340 |
| |
341 void jabber_roster_group_change(GaimConnection *gc, const char *name, |
| |
342 const char *old_group, const char *new_group) |
| |
343 { |
| |
344 GSList *buddies, *groups = NULL; |
| |
345 GaimBuddy *b; |
| |
346 GaimGroup *g; |
| |
347 |
| |
348 if(!old_group || !new_group || !strcmp(old_group, new_group)) |
| |
349 return; |
| |
350 |
| |
351 buddies = gaim_find_buddies(gc->account, name); |
| |
352 while(buddies) { |
| |
353 b = buddies->data; |
| |
354 g = gaim_buddy_get_group(b); |
| |
355 if(!strcmp(g->name, old_group)) |
| |
356 groups = g_slist_append(groups, (char*)new_group); /* ick */ |
| |
357 else |
| |
358 groups = g_slist_append(groups, g->name); |
| |
359 buddies = g_slist_remove(buddies, b); |
| |
360 } |
| |
361 jabber_roster_update(gc->proto_data, name, groups); |
| |
362 g_slist_free(groups); |
| |
363 } |
| |
364 |
| |
365 void jabber_roster_group_rename(GaimConnection *gc, const char *old_name, |
| |
366 GaimGroup *group, GList *moved_buddies) |
| |
367 { |
| |
368 GList *l; |
| |
369 for(l = moved_buddies; l; l = l->next) { |
| |
370 GaimBuddy *buddy = l->data; |
| |
371 jabber_roster_group_change(gc, buddy->name, old_name, group->name); |
| |
372 } |
| |
373 } |
| |
374 |
| |
375 void jabber_roster_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, |
| |
376 GaimGroup *group) { |
| |
377 GSList *buddies = gaim_find_buddies(gc->account, buddy->name); |
| |
378 GSList *groups = NULL; |
| |
379 |
| |
380 buddies = g_slist_remove(buddies, buddy); |
| |
381 if(g_slist_length(buddies)) { |
| |
382 GaimBuddy *tmpbuddy; |
| |
383 GaimGroup *tmpgroup; |
| |
384 |
| |
385 while(buddies) { |
| |
386 tmpbuddy = buddies->data; |
| |
387 tmpgroup = gaim_buddy_get_group(tmpbuddy); |
| |
388 groups = g_slist_append(groups, tmpgroup->name); |
| |
389 buddies = g_slist_remove(buddies, tmpbuddy); |
| |
390 } |
| |
391 |
| |
392 jabber_roster_update(gc->proto_data, buddy->name, groups); |
| |
393 } else { |
| |
394 JabberIq *iq = jabber_iq_new_query(gc->proto_data, JABBER_IQ_SET, |
| |
395 "jabber:iq:roster"); |
| |
396 xmlnode *query = xmlnode_get_child(iq->node, "query"); |
| |
397 xmlnode *item = xmlnode_new_child(query, "item"); |
| |
398 |
| |
399 xmlnode_set_attrib(item, "jid", buddy->name); |
| |
400 xmlnode_set_attrib(item, "subscription", "remove"); |
| |
401 |
| |
402 jabber_iq_send(iq); |
| |
403 } |
| |
404 |
| |
405 if(buddies) |
| |
406 g_slist_free(buddies); |
| |
407 if(groups) |
| |
408 g_slist_free(groups); |
| |
409 } |