1 /*
2 * purple - Jabber Protocol Plugin
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 #include "debug.h"
25 #include "glibcompat.h"
26 #include "imgstore.h"
27 #include "prpl.h"
28 #include "notify.h"
29 #include "request.h"
30 #include "util.h"
31 #include "xmlnode.h"
32
33 #include "buddy.h"
34 #include "chat.h"
35 #include "jabber.h"
36 #include "iq.h"
37 #include "presence.h"
38 #include "useravatar.h"
39 #include "xdata.h"
40 #include "pep.h"
41 #include "adhoccommands.h"
42 #include "google/google.h"
43
44 typedef struct {
45 long idle_seconds;
46 } JabberBuddyInfoResource;
47
48 typedef struct {
49 JabberStream *js;
50 JabberBuddy *jb;
51 char *jid;
52 GSList *ids;
53 GHashTable *resources;
54 guint timeout_handle;
55 GSList *vcard_imgids;
56 PurpleNotifyUserInfo *user_info;
57 long last_seconds;
58 gchar *last_message;
59 } JabberBuddyInfo;
60
61 static void
jabber_buddy_resource_free(JabberBuddyResource * jbr)62 jabber_buddy_resource_free(JabberBuddyResource *jbr)
63 {
64 g_return_if_fail(jbr != NULL);
65
66 jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
67
68 while(jbr->commands) {
69 JabberAdHocCommands *cmd = jbr->commands->data;
70 g_free(cmd->jid);
71 g_free(cmd->node);
72 g_free(cmd->name);
73 g_free(cmd);
74 jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
75 }
76
77 while (jbr->caps.exts) {
78 g_free(jbr->caps.exts->data);
79 jbr->caps.exts = g_list_delete_link(jbr->caps.exts, jbr->caps.exts);
80 }
81
82 g_free(jbr->name);
83 g_free(jbr->status);
84 g_free(jbr->thread_id);
85 g_free(jbr->client.name);
86 g_free(jbr->client.version);
87 g_free(jbr->client.os);
88 g_free(jbr);
89 }
90
jabber_buddy_free(JabberBuddy * jb)91 void jabber_buddy_free(JabberBuddy *jb)
92 {
93 g_return_if_fail(jb != NULL);
94
95 g_free(jb->error_msg);
96 while(jb->resources)
97 jabber_buddy_resource_free(jb->resources->data);
98
99 g_free(jb);
100 }
101
jabber_buddy_find(JabberStream * js,const char * name,gboolean create)102 JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
103 gboolean create)
104 {
105 JabberBuddy *jb;
106 char *realname;
107
108 if (js->buddies == NULL)
109 return NULL;
110
111 if(!(realname = jabber_get_bare_jid(name)))
112 return NULL;
113
114 jb = g_hash_table_lookup(js->buddies, realname);
115
116 if(!jb && create) {
117 jb = g_new0(JabberBuddy, 1);
118 g_hash_table_insert(js->buddies, realname, jb);
119 } else
120 g_free(realname);
121
122 return jb;
123 }
124
125 /* Returns -1 if a is a higher priority resource than b, or is
126 * "more available" than b. 0 if they're the same, and 1 if b is
127 * higher priority/more available than a.
128 */
resource_compare_cb(gconstpointer a,gconstpointer b)129 static gint resource_compare_cb(gconstpointer a, gconstpointer b)
130 {
131 const JabberBuddyResource *jbra = a;
132 const JabberBuddyResource *jbrb = b;
133 JabberBuddyState state_a, state_b;
134
135 if (jbra->priority != jbrb->priority)
136 return jbra->priority > jbrb->priority ? -1 : 1;
137
138 /* Fold the states for easier comparison */
139 /* TODO: Differentiate online/chat and away/dnd? */
140 switch (jbra->state) {
141 case JABBER_BUDDY_STATE_ONLINE:
142 case JABBER_BUDDY_STATE_CHAT:
143 state_a = JABBER_BUDDY_STATE_ONLINE;
144 break;
145 case JABBER_BUDDY_STATE_AWAY:
146 case JABBER_BUDDY_STATE_DND:
147 state_a = JABBER_BUDDY_STATE_AWAY;
148 break;
149 case JABBER_BUDDY_STATE_XA:
150 state_a = JABBER_BUDDY_STATE_XA;
151 break;
152 case JABBER_BUDDY_STATE_UNAVAILABLE:
153 state_a = JABBER_BUDDY_STATE_UNAVAILABLE;
154 break;
155 default:
156 state_a = JABBER_BUDDY_STATE_UNKNOWN;
157 break;
158 }
159
160 switch (jbrb->state) {
161 case JABBER_BUDDY_STATE_ONLINE:
162 case JABBER_BUDDY_STATE_CHAT:
163 state_b = JABBER_BUDDY_STATE_ONLINE;
164 break;
165 case JABBER_BUDDY_STATE_AWAY:
166 case JABBER_BUDDY_STATE_DND:
167 state_b = JABBER_BUDDY_STATE_AWAY;
168 break;
169 case JABBER_BUDDY_STATE_XA:
170 state_b = JABBER_BUDDY_STATE_XA;
171 break;
172 case JABBER_BUDDY_STATE_UNAVAILABLE:
173 state_b = JABBER_BUDDY_STATE_UNAVAILABLE;
174 break;
175 default:
176 state_b = JABBER_BUDDY_STATE_UNKNOWN;
177 break;
178 }
179
180 if (state_a == state_b) {
181 if (jbra->idle == jbrb->idle)
182 return 0;
183 else if ((jbra->idle && !jbrb->idle) ||
184 (jbra->idle && jbrb->idle && jbra->idle < jbrb->idle))
185 return 1;
186 else
187 return -1;
188 }
189
190 if (state_a == JABBER_BUDDY_STATE_ONLINE)
191 return -1;
192 else if (state_a == JABBER_BUDDY_STATE_AWAY &&
193 (state_b == JABBER_BUDDY_STATE_XA ||
194 state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
195 state_b == JABBER_BUDDY_STATE_UNKNOWN))
196 return -1;
197 else if (state_a == JABBER_BUDDY_STATE_XA &&
198 (state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
199 state_b == JABBER_BUDDY_STATE_UNKNOWN))
200 return -1;
201 else if (state_a == JABBER_BUDDY_STATE_UNAVAILABLE &&
202 state_b == JABBER_BUDDY_STATE_UNKNOWN)
203 return -1;
204
205 return 1;
206 }
207
jabber_buddy_find_resource(JabberBuddy * jb,const char * resource)208 JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
209 const char *resource)
210 {
211 GList *l;
212
213 if (!jb)
214 return NULL;
215
216 if (resource == NULL)
217 return jb->resources ? jb->resources->data : NULL;
218
219 for (l = jb->resources; l; l = l->next)
220 {
221 JabberBuddyResource *jbr = l->data;
222 if (purple_strequal(resource, jbr->name))
223 return jbr;
224 }
225
226 return NULL;
227 }
228
jabber_buddy_track_resource(JabberBuddy * jb,const char * resource,int priority,JabberBuddyState state,const char * status)229 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
230 int priority, JabberBuddyState state, const char *status)
231 {
232 /* TODO: Optimization: Only reinsert if priority+state changed */
233 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
234 if (jbr) {
235 jb->resources = g_list_remove(jb->resources, jbr);
236 } else {
237 jbr = g_new0(JabberBuddyResource, 1);
238 jbr->jb = jb;
239 jbr->name = g_strdup(resource);
240 jbr->capabilities = JABBER_CAP_NONE;
241 jbr->tz_off = PURPLE_NO_TZ_OFF;
242 }
243 jbr->priority = priority;
244 jbr->state = state;
245 g_free(jbr->status);
246 jbr->status = g_strdup(status);
247
248 jb->resources = g_list_insert_sorted(jb->resources, jbr,
249 resource_compare_cb);
250 return jbr;
251 }
252
jabber_buddy_remove_resource(JabberBuddy * jb,const char * resource)253 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
254 {
255 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
256
257 if(!jbr)
258 return;
259
260 jabber_buddy_resource_free(jbr);
261 }
262
263 /*******
264 * This is the old vCard stuff taken from the old prpl. vCards, by definition
265 * are a temporary thing until jabber can get its act together and come up
266 * with a format for user information, hence the namespace of 'vcard-temp'
267 *
268 * Since I don't feel like putting that much work into something that's
269 * _supposed_ to go away, i'm going to just copy the kludgy old code here,
270 * and make it purdy when jabber comes up with a standards-track JEP to
271 * replace vcard-temp
272 * --Nathan
273 *******/
274
275 /*---------------------------------------*/
276 /* Jabber "set info" (vCard) support */
277 /*---------------------------------------*/
278
279 /*
280 * V-Card format:
281 *
282 * <vCard prodid='' version='' xmlns=''>
283 * <FN></FN>
284 * <N>
285 * <FAMILY/>
286 * <GIVEN/>
287 * </N>
288 * <NICKNAME/>
289 * <URL/>
290 * <ADR>
291 * <STREET/>
292 * <EXTADD/>
293 * <LOCALITY/>
294 * <REGION/>
295 * <PCODE/>
296 * <COUNTRY/>
297 * </ADR>
298 * <TEL/>
299 * <EMAIL/>
300 * <ORG>
301 * <ORGNAME/>
302 * <ORGUNIT/>
303 * </ORG>
304 * <TITLE/>
305 * <ROLE/>
306 * <DESC/>
307 * <BDAY/>
308 * </vCard>
309 *
310 * See also:
311 *
312 * http://docs.jabber.org/proto/html/vcard-temp.html
313 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
314 */
315
316 /*
317 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
318 * and attributes.
319 *
320 * Order is (or should be) unimportant. For example: we have no way of
321 * knowing in what order real data will arrive.
322 *
323 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
324 * name, XML tag's parent tag "path" (relative to vCard node).
325 *
326 * List is terminated by a NULL label pointer.
327 *
328 * Entries with no label text, but with XML tag and parent tag
329 * entries, are used by V-Card XML construction routines to
330 * "automagically" construct the appropriate XML node tree.
331 *
332 * Thoughts on future direction/expansion
333 *
334 * This is a "simple" vCard.
335 *
336 * It is possible for nodes other than the "vCard" node to have
337 * attributes. Should that prove necessary/desirable, add an
338 * "attributes" pointer to the vcard_template struct, create the
339 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
340 * array.
341 *
342 * The above changes will (obviously) require changes to the vCard
343 * construction routines.
344 */
345
346 struct vcard_template {
347 char *label; /* label text pointer */
348 char *tag; /* tag text */
349 char *ptag; /* parent tag "path" text */
350 } const vcard_template_data[] = {
351 {N_("Full Name"), "FN", NULL},
352 {N_("Family Name"), "FAMILY", "N"},
353 {N_("Given Name"), "GIVEN", "N"},
354 {N_("Nickname"), "NICKNAME", NULL},
355 {N_("URL"), "URL", NULL},
356 {N_("Street Address"), "STREET", "ADR"},
357 {N_("Extended Address"), "EXTADD", "ADR"},
358 {N_("Locality"), "LOCALITY", "ADR"},
359 {N_("Region"), "REGION", "ADR"},
360 {N_("Postal Code"), "PCODE", "ADR"},
361 {N_("Country"), "CTRY", "ADR"},
362 {N_("Telephone"), "NUMBER", "TEL"},
363 {N_("Email"), "USERID", "EMAIL"},
364 {N_("Organization Name"), "ORGNAME", "ORG"},
365 {N_("Organization Unit"), "ORGUNIT", "ORG"},
366 {N_("Job Title"), "TITLE", NULL},
367 {N_("Role"), "ROLE", NULL},
368 {N_("Birthday"), "BDAY", NULL},
369 {N_("Description"), "DESC", NULL},
370 {"", "N", NULL},
371 {"", "ADR", NULL},
372 {"", "ORG", NULL},
373 {NULL, NULL, NULL}
374 };
375
376 /*
377 * The "vCard" tag's attribute list...
378 */
379 struct tag_attr {
380 char *attr;
381 char *value;
382 } const vcard_tag_attr_list[] = {
383 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
384 {"version", "2.0", },
385 {"xmlns", "vcard-temp", },
386 {NULL, NULL},
387 };
388
389
390 /*
391 * Insert a tag node into an xmlnode tree, recursively inserting parent tag
392 * nodes as necessary
393 *
394 * Returns pointer to inserted node
395 *
396 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
397 * calls itself), so don't put any "static"s in here!
398 */
insert_tag_to_parent_tag(xmlnode * start,const char * parent_tag,const char * new_tag)399 static xmlnode *insert_tag_to_parent_tag(xmlnode *start, const char *parent_tag, const char *new_tag)
400 {
401 xmlnode *x = NULL;
402
403 /*
404 * If the parent tag wasn't specified, see if we can get it
405 * from the vCard template struct.
406 */
407 if(parent_tag == NULL) {
408 const struct vcard_template *vc_tp = vcard_template_data;
409
410 while(vc_tp->label != NULL) {
411 if(purple_strequal(vc_tp->tag, new_tag)) {
412 parent_tag = vc_tp->ptag;
413 break;
414 }
415 ++vc_tp;
416 }
417 }
418
419 /*
420 * If we have a parent tag...
421 */
422 if(parent_tag != NULL ) {
423 /*
424 * Try to get the parent node for a tag
425 */
426 if((x = xmlnode_get_child(start, parent_tag)) == NULL) {
427 /*
428 * Descend?
429 */
430 char *grand_parent = g_strdup(parent_tag);
431 char *parent;
432
433 if((parent = strrchr(grand_parent, '/')) != NULL) {
434 *(parent++) = '\0';
435 x = insert_tag_to_parent_tag(start, grand_parent, parent);
436 } else {
437 x = xmlnode_new_child(start, grand_parent);
438 }
439 g_free(grand_parent);
440 } else {
441 /*
442 * We found *something* to be the parent node.
443 * Note: may be the "root" node!
444 */
445 xmlnode *y;
446 if((y = xmlnode_get_child(x, new_tag)) != NULL) {
447 return(y);
448 }
449 }
450 }
451
452 /*
453 * insert the new tag into its parent node
454 */
455 return(xmlnode_new_child((x == NULL? start : x), new_tag));
456 }
457
458 /*
459 * Send vCard info to Jabber server
460 */
jabber_set_info(PurpleConnection * gc,const char * info)461 void jabber_set_info(PurpleConnection *gc, const char *info)
462 {
463 PurpleStoredImage *img;
464 JabberIq *iq;
465 JabberStream *js = purple_connection_get_protocol_data(gc);
466 xmlnode *vc_node;
467 const struct tag_attr *tag_attr;
468
469 /* if we have't grabbed the remote vcard yet, we can't
470 * assume that what we have here is correct */
471 if(!js->vcard_fetched)
472 return;
473
474 if (js->vcard_timer) {
475 purple_timeout_remove(js->vcard_timer);
476 js->vcard_timer = 0;
477 }
478
479 g_free(js->avatar_hash);
480 js->avatar_hash = NULL;
481
482 /*
483 * Send only if there's actually any *information* to send
484 */
485 vc_node = info ? xmlnode_from_str(info, -1) : NULL;
486
487 if (vc_node && (!vc_node->name ||
488 g_ascii_strncasecmp(vc_node->name, "vCard", 5))) {
489 xmlnode_free(vc_node);
490 vc_node = NULL;
491 }
492
493 if ((img = purple_buddy_icons_find_account_icon(gc->account))) {
494 gconstpointer avatar_data;
495 gsize avatar_len;
496 xmlnode *photo, *binval, *type;
497 gchar *enc;
498
499 if(!vc_node) {
500 vc_node = xmlnode_new("vCard");
501 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
502 xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
503 }
504
505 avatar_data = purple_imgstore_get_data(img);
506 avatar_len = purple_imgstore_get_size(img);
507 /* Get rid of an old PHOTO if one exists.
508 * TODO: This may want to be modified to remove all old PHOTO
509 * children, at the moment some people have managed to get
510 * multiple PHOTO entries in their vCard. */
511 if((photo = xmlnode_get_child(vc_node, "PHOTO"))) {
512 xmlnode_free(photo);
513 }
514 photo = xmlnode_new_child(vc_node, "PHOTO");
515 type = xmlnode_new_child(photo, "TYPE");
516 xmlnode_insert_data(type, "image/png", -1);
517 binval = xmlnode_new_child(photo, "BINVAL");
518 enc = purple_base64_encode(avatar_data, avatar_len);
519
520 js->avatar_hash =
521 jabber_calculate_data_hash(avatar_data, avatar_len, "sha1");
522
523 xmlnode_insert_data(binval, enc, -1);
524 g_free(enc);
525 purple_imgstore_unref(img);
526 } else if (vc_node) {
527 xmlnode *photo;
528 /* TODO: Remove all PHOTO children? (see above note) */
529 if ((photo = xmlnode_get_child(vc_node, "PHOTO"))) {
530 xmlnode_free(photo);
531 }
532 }
533
534 if (vc_node != NULL) {
535 iq = jabber_iq_new(js, JABBER_IQ_SET);
536 xmlnode_insert_child(iq->node, vc_node);
537 jabber_iq_send(iq);
538
539 /* Send presence to update vcard-temp:x:update */
540 jabber_presence_send(js, FALSE);
541 }
542 }
543
jabber_set_buddy_icon(PurpleConnection * gc,PurpleStoredImage * img)544 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
545 {
546 PurpleAccount *account = purple_connection_get_account(gc);
547
548 /* Publish the avatar as specified in XEP-0084 */
549 jabber_avatar_set(gc->proto_data, img);
550 /* Set the image in our vCard */
551 jabber_set_info(gc, purple_account_get_user_info(account));
552
553 /* TODO: Fake image to ourselves, since a number of servers do not echo
554 * back our presence to us. To do this without uselessly copying the data
555 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
556 * an existing icon/stored image). */
557 }
558
559 /*
560 * This is the callback from the "ok clicked" for "set vCard"
561 *
562 * Sets the vCard with data from PurpleRequestFields.
563 */
564 static void
jabber_format_info(PurpleConnection * gc,PurpleRequestFields * fields)565 jabber_format_info(PurpleConnection *gc, PurpleRequestFields *fields)
566 {
567 xmlnode *vc_node;
568 PurpleRequestField *field;
569 const char *text;
570 char *p;
571 const struct vcard_template *vc_tp;
572 const struct tag_attr *tag_attr;
573
574 vc_node = xmlnode_new("vCard");
575
576 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
577 xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
578
579 for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) {
580 if (*vc_tp->label == '\0')
581 continue;
582
583 field = purple_request_fields_get_field(fields, vc_tp->tag);
584 text = purple_request_field_string_get_value(field);
585
586
587 if (text != NULL && *text != '\0') {
588 xmlnode *xp;
589
590 purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text);
591
592 if ((xp = insert_tag_to_parent_tag(vc_node,
593 NULL, vc_tp->tag)) != NULL) {
594
595 xmlnode_insert_data(xp, text, -1);
596 }
597 }
598 }
599
600 p = xmlnode_to_str(vc_node, NULL);
601 xmlnode_free(vc_node);
602
603 purple_account_set_user_info(purple_connection_get_account(gc), p);
604 serv_set_info(gc, p);
605
606 g_free(p);
607 }
608
609 /*
610 * This gets executed by the proto action
611 *
612 * Creates a new PurpleRequestFields struct, gets the XML-formatted user_info
613 * string (if any) into GSLists for the (multi-entry) edit dialog and
614 * calls the set_vcard dialog.
615 */
jabber_setup_set_info(PurplePluginAction * action)616 void jabber_setup_set_info(PurplePluginAction *action)
617 {
618 PurpleConnection *gc = (PurpleConnection *) action->context;
619 PurpleRequestFields *fields;
620 PurpleRequestFieldGroup *group;
621 PurpleRequestField *field;
622 const struct vcard_template *vc_tp;
623 const char *user_info;
624 char *cdata = NULL;
625 xmlnode *x_vc_data = NULL;
626
627 fields = purple_request_fields_new();
628 group = purple_request_field_group_new(NULL);
629 purple_request_fields_add_group(fields, group);
630
631 /*
632 * Get existing, XML-formatted, user info
633 */
634 if((user_info = purple_account_get_user_info(gc->account)) != NULL)
635 x_vc_data = xmlnode_from_str(user_info, -1);
636
637 /*
638 * Set up GSLists for edit with labels from "template," data from user info
639 */
640 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
641 xmlnode *data_node;
642 if((vc_tp->label)[0] == '\0')
643 continue;
644
645 if (x_vc_data != NULL) {
646 if(vc_tp->ptag == NULL) {
647 data_node = xmlnode_get_child(x_vc_data, vc_tp->tag);
648 } else {
649 gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
650 data_node = xmlnode_get_child(x_vc_data, tag);
651 g_free(tag);
652 }
653 if(data_node)
654 cdata = xmlnode_get_data(data_node);
655 }
656
657 if(purple_strequal(vc_tp->tag, "DESC")) {
658 field = purple_request_field_string_new(vc_tp->tag,
659 _(vc_tp->label), cdata,
660 TRUE);
661 } else {
662 field = purple_request_field_string_new(vc_tp->tag,
663 _(vc_tp->label), cdata,
664 FALSE);
665 }
666
667 g_free(cdata);
668 cdata = NULL;
669
670 purple_request_field_group_add_field(group, field);
671 }
672
673 if(x_vc_data != NULL)
674 xmlnode_free(x_vc_data);
675
676 purple_request_fields(gc, _("Edit XMPP vCard"),
677 _("Edit XMPP vCard"),
678 _("All items below are optional. Enter only the "
679 "information with which you feel comfortable."),
680 fields,
681 _("Save"), G_CALLBACK(jabber_format_info),
682 _("Cancel"), NULL,
683 purple_connection_get_account(gc), NULL, NULL,
684 gc);
685 }
686
687 /*---------------------------------------*/
688 /* End Jabber "set info" (vCard) support */
689 /*---------------------------------------*/
690
691 /******
692 * end of that ancient crap that needs to die
693 ******/
694
jabber_buddy_info_destroy(JabberBuddyInfo * jbi)695 static void jabber_buddy_info_destroy(JabberBuddyInfo *jbi)
696 {
697 /* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */
698 if (jbi->timeout_handle > 0)
699 purple_timeout_remove(jbi->timeout_handle);
700
701 g_free(jbi->jid);
702 g_hash_table_destroy(jbi->resources);
703 g_free(jbi->last_message);
704 purple_notify_user_info_destroy(jbi->user_info);
705 g_free(jbi);
706 }
707
708 static void
add_jbr_info(JabberBuddyInfo * jbi,const char * resource,JabberBuddyResource * jbr)709 add_jbr_info(JabberBuddyInfo *jbi, const char *resource,
710 JabberBuddyResource *jbr)
711 {
712 JabberBuddyInfoResource *jbir;
713 PurpleNotifyUserInfo *user_info;
714
715 jbir = g_hash_table_lookup(jbi->resources, resource);
716 user_info = jbi->user_info;
717
718 if (jbr && jbr->client.name) {
719 char *tmp =
720 g_strdup_printf("%s%s%s", jbr->client.name,
721 (jbr->client.version ? " " : ""),
722 (jbr->client.version ? jbr->client.version : ""));
723 purple_notify_user_info_prepend_pair(user_info, _("Client"), tmp);
724 g_free(tmp);
725
726 if (jbr->client.os)
727 purple_notify_user_info_prepend_pair(user_info, _("Operating System"), jbr->client.os);
728 }
729
730 if (jbr && jbr->tz_off != PURPLE_NO_TZ_OFF) {
731 time_t now_t;
732 struct tm *now;
733 char *timestamp;
734 time(&now_t);
735 now_t += jbr->tz_off;
736 now = gmtime(&now_t);
737
738 timestamp =
739 g_strdup_printf("%s %c%02d%02d", purple_time_format(now),
740 jbr->tz_off < 0 ? '-' : '+',
741 abs(jbr->tz_off / (60*60)),
742 abs((jbr->tz_off % (60*60)) / 60));
743 purple_notify_user_info_prepend_pair(user_info, _("Local Time"), timestamp);
744 g_free(timestamp);
745 }
746
747 if (jbir && jbir->idle_seconds > 0) {
748 char *idle = purple_str_seconds_to_string(jbir->idle_seconds);
749 purple_notify_user_info_prepend_pair(user_info, _("Idle"), idle);
750 g_free(idle);
751 }
752
753 if (jbr) {
754 char *purdy = NULL;
755 char *tmp;
756 char priority[12];
757 const char *status_name = jabber_buddy_state_get_name(jbr->state);
758
759 if (jbr->status) {
760 tmp = purple_markup_escape_text(jbr->status, -1);
761 purdy = purple_strdup_withhtml(tmp);
762 g_free(tmp);
763
764 if (purple_strequal(status_name, purdy))
765 status_name = NULL;
766 }
767
768 tmp = g_strdup_printf("%s%s%s", (status_name ? status_name : ""),
769 ((status_name && purdy) ? ": " : ""),
770 (purdy ? purdy : ""));
771 purple_notify_user_info_prepend_pair(user_info, _("Status"), tmp);
772
773 g_snprintf(priority, sizeof(priority), "%d", jbr->priority);
774 purple_notify_user_info_prepend_pair(user_info, _("Priority"), priority);
775
776 g_free(tmp);
777 g_free(purdy);
778 } else {
779 purple_notify_user_info_prepend_pair(user_info, _("Status"), _("Unknown"));
780 }
781 }
782
jabber_buddy_info_show_if_ready(JabberBuddyInfo * jbi)783 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
784 {
785 char *resource_name;
786 JabberBuddyResource *jbr;
787 GList *resources;
788 PurpleNotifyUserInfo *user_info;
789
790 /* not yet */
791 if (jbi->ids)
792 return;
793
794 user_info = jbi->user_info;
795 resource_name = jabber_get_resource(jbi->jid);
796
797 /* If we have one or more pairs from the vcard, put a section break above it */
798 if (purple_notify_user_info_get_entries(user_info))
799 purple_notify_user_info_prepend_section_break(user_info);
800
801 /* Add the information about the user's resource(s) */
802 if (resource_name) {
803 jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
804 add_jbr_info(jbi, resource_name, jbr);
805 } else {
806 /* TODO: This is in priority-ascending order (lowest prio first), because
807 * everything is prepended. Is that ok? */
808 for (resources = jbi->jb->resources; resources; resources = resources->next) {
809 jbr = resources->data;
810
811 /* put a section break between resources, this is not needed if
812 we are at the first, because one was already added for the vcard
813 section */
814 if (resources != jbi->jb->resources)
815 purple_notify_user_info_prepend_section_break(user_info);
816
817 add_jbr_info(jbi, jbr->name, jbr);
818
819 if (jbr->name)
820 purple_notify_user_info_prepend_pair(user_info, _("Resource"), jbr->name);
821 }
822 }
823
824 if (!jbi->jb->resources) {
825 /* the buddy is offline */
826 gboolean is_domain = jabber_jid_is_domain(jbi->jid);
827
828 if (jbi->last_seconds > 0) {
829 char *last = purple_str_seconds_to_string(jbi->last_seconds);
830 gchar *message = NULL;
831 const gchar *title = NULL;
832 if (is_domain) {
833 title = _("Uptime");
834 message = last;
835 last = NULL;
836 } else {
837 title = _("Logged Off");
838 message = g_strdup_printf(_("%s ago"), last);
839 }
840 purple_notify_user_info_prepend_pair(user_info, title, message);
841 g_free(last);
842 g_free(message);
843 }
844
845 if (!is_domain) {
846 gchar *status =
847 g_strdup_printf("%s%s%s", _("Offline"),
848 jbi->last_message ? ": " : "",
849 jbi->last_message ? jbi->last_message : "");
850 purple_notify_user_info_prepend_pair(user_info, _("Status"), status);
851 g_free(status);
852 }
853 }
854
855 g_free(resource_name);
856
857 purple_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL);
858
859 while (jbi->vcard_imgids) {
860 purple_imgstore_unref_by_id(GPOINTER_TO_INT(jbi->vcard_imgids->data));
861 jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids);
862 }
863
864 jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
865
866 jabber_buddy_info_destroy(jbi);
867 }
868
jabber_buddy_info_remove_id(JabberBuddyInfo * jbi,const char * id)869 static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
870 {
871 GSList *l = jbi->ids;
872 char *comp_id;
873
874 if(!id)
875 return;
876
877 while(l) {
878 comp_id = l->data;
879 if(purple_strequal(id, comp_id)) {
880 jbi->ids = g_slist_remove(jbi->ids, comp_id);
881 g_free(comp_id);
882 return;
883 }
884 l = l->next;
885 }
886 }
887
888 static gboolean
set_own_vcard_cb(gpointer data)889 set_own_vcard_cb(gpointer data)
890 {
891 JabberStream *js = data;
892 PurpleAccount *account = purple_connection_get_account(js->gc);
893
894 js->vcard_timer = 0;
895
896 jabber_set_info(js->gc, purple_account_get_user_info(account));
897
898 return FALSE;
899 }
900
jabber_vcard_save_mine(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)901 static void jabber_vcard_save_mine(JabberStream *js, const char *from,
902 JabberIqType type, const char *id,
903 xmlnode *packet, gpointer data)
904 {
905 xmlnode *vcard, *photo, *binval;
906 char *txt, *vcard_hash = NULL;
907 PurpleAccount *account;
908
909 if (type == JABBER_IQ_ERROR) {
910 xmlnode *error;
911 purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
912
913 error = xmlnode_get_child(packet, "error");
914 if (!error || !xmlnode_get_child(error, "item-not-found"))
915 return;
916 }
917
918 account = purple_connection_get_account(js->gc);
919
920 if((vcard = xmlnode_get_child(packet, "vCard")) ||
921 (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
922 {
923 txt = xmlnode_to_str(vcard, NULL);
924 purple_account_set_user_info(account, txt);
925 g_free(txt);
926 } else {
927 /* if we have no vCard, then lets not overwrite what we might have locally */
928 }
929
930 js->vcard_fetched = TRUE;
931
932 if (vcard && (photo = xmlnode_get_child(vcard, "PHOTO")) &&
933 (binval = xmlnode_get_child(photo, "BINVAL"))) {
934 gsize size;
935 char *bintext = xmlnode_get_data(binval);
936 if (bintext) {
937 guchar *data = purple_base64_decode(bintext, &size);
938 g_free(bintext);
939
940 if (data) {
941 vcard_hash = jabber_calculate_data_hash(data, size, "sha1");
942 g_free(data);
943 }
944 }
945 }
946
947 /* Republish our vcard if the photo is different than the server's */
948 if (js->initial_avatar_hash && !purple_strequal(vcard_hash, js->initial_avatar_hash)) {
949 /*
950 * Google Talk has developed the behavior that it will not accept
951 * a vcard set in the first 10 seconds (or so) of the connection;
952 * it returns an error (namespaces trimmed):
953 * <error code="500" type="wait"><internal-server-error/></error>.
954 */
955 if (js->googletalk)
956 js->vcard_timer = purple_timeout_add_seconds(10, set_own_vcard_cb,
957 js);
958 else
959 jabber_set_info(js->gc, purple_account_get_user_info(account));
960 } else if (vcard_hash) {
961 /* A photo is in the vCard. Advertise its hash */
962 js->avatar_hash = vcard_hash;
963 vcard_hash = NULL;
964
965 /* Send presence to update vcard-temp:x:update */
966 jabber_presence_send(js, FALSE);
967 }
968
969 g_free(vcard_hash);
970 }
971
jabber_vcard_fetch_mine(JabberStream * js)972 void jabber_vcard_fetch_mine(JabberStream *js)
973 {
974 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
975
976 xmlnode *vcard = xmlnode_new_child(iq->node, "vCard");
977 xmlnode_set_namespace(vcard, "vcard-temp");
978 jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
979
980 jabber_iq_send(iq);
981 }
982
jabber_vcard_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)983 static void jabber_vcard_parse(JabberStream *js, const char *from,
984 JabberIqType type, const char *id,
985 xmlnode *packet, gpointer data)
986 {
987 char *bare_jid;
988 char *text;
989 char *serverside_alias = NULL;
990 xmlnode *vcard;
991 PurpleAccount *account;
992 JabberBuddyInfo *jbi = data;
993 PurpleNotifyUserInfo *user_info;
994
995 g_return_if_fail(jbi != NULL);
996
997 jabber_buddy_info_remove_id(jbi, id);
998
999 if (type == JABBER_IQ_ERROR) {
1000 purple_debug_info("jabber", "Got error response for vCard\n");
1001 jabber_buddy_info_show_if_ready(jbi);
1002 return;
1003 }
1004
1005 user_info = jbi->user_info;
1006 account = purple_connection_get_account(js->gc);
1007 bare_jid = jabber_get_bare_jid(from ? from : purple_account_get_username(account));
1008
1009 /* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1010 if((vcard = xmlnode_get_child(packet, "vCard")) ||
1011 (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
1012 xmlnode *child;
1013 for(child = vcard->child; child; child = child->next)
1014 {
1015 xmlnode *child2;
1016
1017 if(child->type != XMLNODE_TYPE_TAG)
1018 continue;
1019
1020 text = xmlnode_get_data(child);
1021 if(text && purple_strequal(child->name, "FN")) {
1022 if (!serverside_alias)
1023 serverside_alias = g_strdup(text);
1024
1025 purple_notify_user_info_add_pair_plaintext(user_info, _("Full Name"), text);
1026 } else if(purple_strequal(child->name, "N")) {
1027 for(child2 = child->child; child2; child2 = child2->next)
1028 {
1029 char *text2;
1030
1031 if(child2->type != XMLNODE_TYPE_TAG)
1032 continue;
1033
1034 text2 = xmlnode_get_data(child2);
1035 if(text2 && purple_strequal(child2->name, "FAMILY")) {
1036 purple_notify_user_info_add_pair_plaintext(user_info, _("Family Name"), text2);
1037 } else if(text2 && purple_strequal(child2->name, "GIVEN")) {
1038 purple_notify_user_info_add_pair_plaintext(user_info, _("Given Name"), text2);
1039 } else if(text2 && purple_strequal(child2->name, "MIDDLE")) {
1040 purple_notify_user_info_add_pair_plaintext(user_info, _("Middle Name"), text2);
1041 }
1042 g_free(text2);
1043 }
1044 } else if(text && purple_strequal(child->name, "NICKNAME")) {
1045 /* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1046 * Ignore it if it's part of the jid. */
1047 if (strstr(bare_jid, text) == NULL) {
1048 g_free(serverside_alias);
1049 serverside_alias = g_strdup(text);
1050
1051 purple_notify_user_info_add_pair_plaintext(user_info, _("Nickname"), text);
1052 }
1053 } else if(text && purple_strequal(child->name, "BDAY")) {
1054 purple_notify_user_info_add_pair_plaintext(user_info, _("Birthday"), text);
1055 } else if(purple_strequal(child->name, "ADR")) {
1056 gboolean address_line_added = FALSE;
1057
1058 for(child2 = child->child; child2; child2 = child2->next)
1059 {
1060 char *text2;
1061
1062 if(child2->type != XMLNODE_TYPE_TAG)
1063 continue;
1064
1065 text2 = xmlnode_get_data(child2);
1066 if (text2 == NULL)
1067 continue;
1068
1069 /* We do this here so that it's not added if all the child
1070 * elements are empty. */
1071 if (!address_line_added)
1072 {
1073 purple_notify_user_info_add_section_header(user_info, _("Address"));
1074 address_line_added = TRUE;
1075 }
1076
1077 if(purple_strequal(child2->name, "POBOX")) {
1078 purple_notify_user_info_add_pair_plaintext(user_info, _("P.O. Box"), text2);
1079 } else if (purple_strequal(child2->name, "EXTADD") || purple_strequal(child2->name, "EXTADR")) {
1080 /*
1081 * EXTADD is correct, EXTADR is generated by other
1082 * clients. The next time someone reads this, remove
1083 * EXTADR.
1084 */
1085 purple_notify_user_info_add_pair_plaintext(user_info, _("Extended Address"), text2);
1086 } else if(purple_strequal(child2->name, "STREET")) {
1087 purple_notify_user_info_add_pair_plaintext(user_info, _("Street Address"), text2);
1088 } else if(purple_strequal(child2->name, "LOCALITY")) {
1089 purple_notify_user_info_add_pair_plaintext(user_info, _("Locality"), text2);
1090 } else if(purple_strequal(child2->name, "REGION")) {
1091 purple_notify_user_info_add_pair_plaintext(user_info, _("Region"), text2);
1092 } else if(purple_strequal(child2->name, "PCODE")) {
1093 purple_notify_user_info_add_pair_plaintext(user_info, _("Postal Code"), text2);
1094 } else if(purple_strequal(child2->name, "CTRY")
1095 || purple_strequal(child2->name, "COUNTRY")) {
1096 purple_notify_user_info_add_pair_plaintext(user_info, _("Country"), text2);
1097 }
1098 g_free(text2);
1099 }
1100
1101 if (address_line_added)
1102 purple_notify_user_info_add_section_break(user_info);
1103
1104 } else if(purple_strequal(child->name, "TEL")) {
1105 char *number;
1106 if((child2 = xmlnode_get_child(child, "NUMBER"))) {
1107 /* show what kind of number it is */
1108 number = xmlnode_get_data(child2);
1109 if(number) {
1110 purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1111 g_free(number);
1112 }
1113 } else if((number = xmlnode_get_data(child))) {
1114 /* lots of clients (including purple) do this, but it's
1115 * out of spec */
1116 purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1117 g_free(number);
1118 }
1119 } else if(purple_strequal(child->name, "EMAIL")) {
1120 char *userid, *escaped;
1121 if((child2 = xmlnode_get_child(child, "USERID"))) {
1122 /* show what kind of email it is */
1123 userid = xmlnode_get_data(child2);
1124 if(userid) {
1125 char *mailto;
1126 escaped = g_markup_escape_text(userid, -1);
1127 mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1128 purple_notify_user_info_add_pair(user_info, _("Email"), mailto);
1129
1130 g_free(mailto);
1131 g_free(escaped);
1132 g_free(userid);
1133 }
1134 } else if((userid = xmlnode_get_data(child))) {
1135 /* lots of clients (including purple) do this, but it's
1136 * out of spec */
1137 char *mailto;
1138
1139 escaped = g_markup_escape_text(userid, -1);
1140 mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1141 purple_notify_user_info_add_pair(user_info, _("Email"), mailto);
1142
1143 g_free(mailto);
1144 g_free(escaped);
1145 g_free(userid);
1146 }
1147 } else if(purple_strequal(child->name, "ORG")) {
1148 for(child2 = child->child; child2; child2 = child2->next)
1149 {
1150 char *text2;
1151
1152 if(child2->type != XMLNODE_TYPE_TAG)
1153 continue;
1154
1155 text2 = xmlnode_get_data(child2);
1156 if(text2 && purple_strequal(child2->name, "ORGNAME")) {
1157 purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Name"), text2);
1158 } else if(text2 && purple_strequal(child2->name, "ORGUNIT")) {
1159 purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Unit"), text2);
1160 }
1161 g_free(text2);
1162 }
1163 } else if(text && purple_strequal(child->name, "TITLE")) {
1164 purple_notify_user_info_add_pair_plaintext(user_info, _("Job Title"), text);
1165 } else if(text && purple_strequal(child->name, "ROLE")) {
1166 purple_notify_user_info_add_pair_plaintext(user_info, _("Role"), text);
1167 } else if(text && purple_strequal(child->name, "DESC")) {
1168 purple_notify_user_info_add_pair_plaintext(user_info, _("Description"), text);
1169 } else if(purple_strequal(child->name, "PHOTO") ||
1170 purple_strequal(child->name, "LOGO")) {
1171 char *bintext = NULL;
1172 xmlnode *binval;
1173
1174 if ((binval = xmlnode_get_child(child, "BINVAL")) &&
1175 (bintext = xmlnode_get_data(binval))) {
1176 gsize size;
1177 guchar *data;
1178 gboolean photo = purple_strequal(child->name, "PHOTO");
1179
1180 data = purple_base64_decode(bintext, &size);
1181 if (data) {
1182 char *img_text;
1183 char *hash;
1184
1185 jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(purple_imgstore_add_with_id(g_memdup2(data, size), size, "logo.png")));
1186 img_text = g_strdup_printf("<img id='%d'>", GPOINTER_TO_INT(jbi->vcard_imgids->data));
1187
1188 purple_notify_user_info_add_pair(user_info, (photo ? _("Photo") : _("Logo")), img_text);
1189
1190 hash = jabber_calculate_data_hash(data, size, "sha1");
1191 purple_buddy_icons_set_for_user(account, bare_jid, data, size, hash);
1192 g_free(hash);
1193 g_free(img_text);
1194 }
1195 g_free(bintext);
1196 }
1197 }
1198 g_free(text);
1199 }
1200 }
1201
1202 if (serverside_alias) {
1203 PurpleBuddy *b;
1204 /* If we found a serverside alias, set it and tell the core */
1205 serv_got_alias(js->gc, bare_jid, serverside_alias);
1206 b = purple_find_buddy(account, bare_jid);
1207 if (b) {
1208 purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", serverside_alias);
1209 }
1210
1211 g_free(serverside_alias);
1212 }
1213
1214 g_free(bare_jid);
1215
1216 jabber_buddy_info_show_if_ready(jbi);
1217 }
1218
jabber_buddy_info_resource_free(gpointer data)1219 static void jabber_buddy_info_resource_free(gpointer data)
1220 {
1221 JabberBuddyInfoResource *jbri = data;
1222 g_free(jbri);
1223 }
1224
jbir_hash(gconstpointer v)1225 static guint jbir_hash(gconstpointer v)
1226 {
1227 if (v)
1228 return g_str_hash(v);
1229 else
1230 return 0;
1231 }
1232
jbir_equal(gconstpointer v1,gconstpointer v2)1233 static gboolean jbir_equal(gconstpointer v1, gconstpointer v2)
1234 {
1235 const gchar *resource_1 = v1;
1236 const gchar *resource_2 = v2;
1237
1238 return purple_strequal(resource_1, resource_2);
1239 }
1240
jabber_version_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1241 static void jabber_version_parse(JabberStream *js, const char *from,
1242 JabberIqType type, const char *id,
1243 xmlnode *packet, gpointer data)
1244 {
1245 JabberBuddyInfo *jbi = data;
1246 xmlnode *query;
1247 char *resource_name;
1248
1249 g_return_if_fail(jbi != NULL);
1250
1251 jabber_buddy_info_remove_id(jbi, id);
1252
1253 if(!from)
1254 return;
1255
1256 resource_name = jabber_get_resource(from);
1257
1258 if(resource_name) {
1259 if (type == JABBER_IQ_RESULT) {
1260 if((query = xmlnode_get_child(packet, "query"))) {
1261 JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
1262 if(jbr) {
1263 xmlnode *node;
1264 if((node = xmlnode_get_child(query, "name"))) {
1265 jbr->client.name = xmlnode_get_data(node);
1266 }
1267 if((node = xmlnode_get_child(query, "version"))) {
1268 jbr->client.version = xmlnode_get_data(node);
1269 }
1270 if((node = xmlnode_get_child(query, "os"))) {
1271 jbr->client.os = xmlnode_get_data(node);
1272 }
1273 }
1274 }
1275 }
1276 g_free(resource_name);
1277 }
1278
1279 jabber_buddy_info_show_if_ready(jbi);
1280 }
1281
jabber_last_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1282 static void jabber_last_parse(JabberStream *js, const char *from,
1283 JabberIqType type, const char *id,
1284 xmlnode *packet, gpointer data)
1285 {
1286 JabberBuddyInfo *jbi = data;
1287 xmlnode *query;
1288 char *resource_name;
1289 const char *seconds;
1290
1291 g_return_if_fail(jbi != NULL);
1292
1293 jabber_buddy_info_remove_id(jbi, id);
1294
1295 if(!from)
1296 return;
1297
1298 resource_name = jabber_get_resource(from);
1299
1300 if(resource_name) {
1301 if (type == JABBER_IQ_RESULT) {
1302 if((query = xmlnode_get_child(packet, "query"))) {
1303 seconds = xmlnode_get_attrib(query, "seconds");
1304 if(seconds) {
1305 char *end = NULL;
1306 long sec = strtol(seconds, &end, 10);
1307 JabberBuddy *jb = NULL;
1308 char *resource = NULL;
1309 char *buddy_name = NULL;
1310 JabberBuddyResource *jbr = NULL;
1311
1312 if(end != seconds) {
1313 JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
1314 if(jbir) {
1315 jbir->idle_seconds = sec;
1316 }
1317 }
1318 /* Update the idle time of the buddy resource, if we got it.
1319 This will correct the value when a server doesn't mark
1320 delayed presence and we got the presence when signing on */
1321 jb = jabber_buddy_find(js, from, FALSE);
1322 if (jb) {
1323 resource = jabber_get_resource(from);
1324 buddy_name = jabber_get_bare_jid(from);
1325 /* if the resource already has an idle time set, we
1326 must have gotten it originally from a presence. In
1327 this case we update it. Otherwise don't update it, to
1328 avoid setting an idle and not getting informed about
1329 the resource getting unidle */
1330 if (resource && buddy_name) {
1331 jbr = jabber_buddy_find_resource(jb, resource);
1332 if (jbr) {
1333 if (jbr->idle) {
1334 if (sec) {
1335 jbr->idle = time(NULL) - sec;
1336 } else {
1337 jbr->idle = 0;
1338 }
1339
1340 if (jbr ==
1341 jabber_buddy_find_resource(jb, NULL)) {
1342 purple_prpl_got_user_idle(js->gc->account,
1343 buddy_name, jbr->idle, jbr->idle);
1344 }
1345 }
1346 }
1347 }
1348 g_free(resource);
1349 g_free(buddy_name);
1350 }
1351 }
1352 }
1353 }
1354 g_free(resource_name);
1355 }
1356
1357 jabber_buddy_info_show_if_ready(jbi);
1358 }
1359
jabber_last_offline_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1360 static void jabber_last_offline_parse(JabberStream *js, const char *from,
1361 JabberIqType type, const char *id,
1362 xmlnode *packet, gpointer data)
1363 {
1364 JabberBuddyInfo *jbi = data;
1365 xmlnode *query;
1366 const char *seconds;
1367
1368 g_return_if_fail(jbi != NULL);
1369
1370 jabber_buddy_info_remove_id(jbi, id);
1371
1372 if (type == JABBER_IQ_RESULT) {
1373 if((query = xmlnode_get_child(packet, "query"))) {
1374 seconds = xmlnode_get_attrib(query, "seconds");
1375 if(seconds) {
1376 char *end = NULL;
1377 long sec = strtol(seconds, &end, 10);
1378 if(end != seconds) {
1379 jbi->last_seconds = sec;
1380 }
1381 }
1382 jbi->last_message = xmlnode_get_data(query);
1383 }
1384 }
1385
1386 jabber_buddy_info_show_if_ready(jbi);
1387 }
1388
jabber_time_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1389 static void jabber_time_parse(JabberStream *js, const char *from,
1390 JabberIqType type, const char *id,
1391 xmlnode *packet, gpointer data)
1392 {
1393 JabberBuddyInfo *jbi = data;
1394 JabberBuddyResource *jbr;
1395 char *resource_name;
1396
1397 g_return_if_fail(jbi != NULL);
1398
1399 jabber_buddy_info_remove_id(jbi, id);
1400
1401 if (!from)
1402 return;
1403
1404 resource_name = jabber_get_resource(from);
1405 jbr = resource_name ? jabber_buddy_find_resource(jbi->jb, resource_name) : NULL;
1406 g_free(resource_name);
1407 if (jbr) {
1408 if (type == JABBER_IQ_RESULT) {
1409 xmlnode *time = xmlnode_get_child(packet, "time");
1410 xmlnode *tzo = time ? xmlnode_get_child(time, "tzo") : NULL;
1411 char *tzo_data = tzo ? xmlnode_get_data(tzo) : NULL;
1412 if (tzo_data) {
1413 char *c = tzo_data;
1414 int hours, minutes;
1415 if (tzo_data[0] == 'Z' && tzo_data[1] == '\0') {
1416 jbr->tz_off = 0;
1417 } else {
1418 gboolean offset_positive = (tzo_data[0] == '+');
1419 /* [+-]HH:MM */
1420 if (((*c == '+' || *c == '-') && (c = c + 1)) &&
1421 sscanf(c, "%02d:%02d", &hours, &minutes) == 2) {
1422 jbr->tz_off = 60*60*hours + 60*minutes;
1423 if (!offset_positive)
1424 jbr->tz_off *= -1;
1425 } else {
1426 purple_debug_info("jabber", "Ignoring malformed timezone %s",
1427 tzo_data);
1428 }
1429 }
1430
1431 g_free(tzo_data);
1432 }
1433 }
1434 }
1435
1436 jabber_buddy_info_show_if_ready(jbi);
1437 }
1438
jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream * js)1439 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js)
1440 {
1441 if (js->pending_buddy_info_requests)
1442 {
1443 JabberBuddyInfo *jbi;
1444 GSList *l = js->pending_buddy_info_requests;
1445 while (l) {
1446 jbi = l->data;
1447
1448 g_slist_free(jbi->ids);
1449 jabber_buddy_info_destroy(jbi);
1450
1451 l = l->next;
1452 }
1453
1454 g_slist_free(js->pending_buddy_info_requests);
1455 js->pending_buddy_info_requests = NULL;
1456 }
1457 }
1458
jabber_buddy_get_info_timeout(gpointer data)1459 static gboolean jabber_buddy_get_info_timeout(gpointer data)
1460 {
1461 JabberBuddyInfo *jbi = data;
1462
1463 /* remove the pending callbacks */
1464 while(jbi->ids) {
1465 char *id = jbi->ids->data;
1466 jabber_iq_remove_callback_by_id(jbi->js, id);
1467 jbi->ids = g_slist_remove(jbi->ids, id);
1468 g_free(id);
1469 }
1470
1471 jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
1472 jbi->timeout_handle = 0;
1473
1474 jabber_buddy_info_show_if_ready(jbi);
1475
1476 return FALSE;
1477 }
1478
_client_is_blacklisted(JabberBuddyResource * jbr,const char * ns)1479 static gboolean _client_is_blacklisted(JabberBuddyResource *jbr, const char *ns)
1480 {
1481 /* can't be blacklisted if we don't know what you're running yet */
1482 if(!jbr->client.name)
1483 return FALSE;
1484
1485 if(purple_strequal(ns, NS_LAST_ACTIVITY)) {
1486 if(purple_strequal(jbr->client.name, "Trillian")) {
1487 /* verified by nwalp 2007/05/09 */
1488 if(purple_strequal(jbr->client.version, "3.1.0.121") ||
1489 /* verified by nwalp 2007/09/19 */
1490 purple_strequal(jbr->client.version, "3.1.7.0")) {
1491 return TRUE;
1492 }
1493 }
1494 }
1495
1496 return FALSE;
1497 }
1498
1499 static void
dispatch_queries_for_resource(JabberStream * js,JabberBuddyInfo * jbi,gboolean is_bare_jid,const char * jid,JabberBuddyResource * jbr)1500 dispatch_queries_for_resource(JabberStream *js, JabberBuddyInfo *jbi,
1501 gboolean is_bare_jid, const char *jid,
1502 JabberBuddyResource *jbr)
1503 {
1504 JabberIq *iq;
1505 JabberBuddyInfoResource *jbir;
1506 char *full_jid = NULL;
1507 const char *to;
1508
1509 if (is_bare_jid && jbr->name) {
1510 full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
1511 to = full_jid;
1512 } else
1513 to = jid;
1514
1515 jbir = g_new0(JabberBuddyInfoResource, 1);
1516 g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir);
1517
1518 if(!jbr->client.name) {
1519 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version");
1520 xmlnode_set_attrib(iq->node, "to", to);
1521 jabber_iq_set_callback(iq, jabber_version_parse, jbi);
1522 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1523 jabber_iq_send(iq);
1524 }
1525
1526 /* this is to fix the feeling of irritation I get when trying
1527 * to get info on a friend running Trillian, which doesn't
1528 * respond (with an error or otherwise) to jabber:iq:last
1529 * requests. There are a number of Trillian users in my
1530 * office. */
1531 if(!_client_is_blacklisted(jbr, NS_LAST_ACTIVITY)) {
1532 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1533 xmlnode_set_attrib(iq->node, "to", to);
1534 jabber_iq_set_callback(iq, jabber_last_parse, jbi);
1535 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1536 jabber_iq_send(iq);
1537 }
1538
1539 if (jbr->tz_off == PURPLE_NO_TZ_OFF &&
1540 (!jbr->caps.info ||
1541 jabber_resource_has_capability(jbr, NS_ENTITY_TIME))) {
1542 xmlnode *child;
1543 iq = jabber_iq_new(js, JABBER_IQ_GET);
1544 xmlnode_set_attrib(iq->node, "to", to);
1545 child = xmlnode_new_child(iq->node, "time");
1546 xmlnode_set_namespace(child, NS_ENTITY_TIME);
1547 jabber_iq_set_callback(iq, jabber_time_parse, jbi);
1548 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1549 jabber_iq_send(iq);
1550 }
1551
1552 g_free(full_jid);
1553 }
1554
jabber_buddy_get_info_for_jid(JabberStream * js,const char * jid)1555 static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
1556 {
1557 JabberIq *iq;
1558 xmlnode *vcard;
1559 GList *resources;
1560 JabberBuddy *jb;
1561 JabberBuddyInfo *jbi;
1562 const char *slash;
1563 gboolean is_bare_jid;
1564
1565 jb = jabber_buddy_find(js, jid, TRUE);
1566
1567 /* invalid JID */
1568 if(!jb)
1569 return;
1570
1571 slash = strchr(jid, '/');
1572 is_bare_jid = (slash == NULL);
1573
1574 jbi = g_new0(JabberBuddyInfo, 1);
1575 jbi->jid = g_strdup(jid);
1576 jbi->js = js;
1577 jbi->jb = jb;
1578 jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free);
1579 jbi->user_info = purple_notify_user_info_new();
1580
1581 iq = jabber_iq_new(js, JABBER_IQ_GET);
1582
1583 xmlnode_set_attrib(iq->node, "to", jid);
1584 vcard = xmlnode_new_child(iq->node, "vCard");
1585 xmlnode_set_namespace(vcard, "vcard-temp");
1586
1587 jabber_iq_set_callback(iq, jabber_vcard_parse, jbi);
1588 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1589
1590 jabber_iq_send(iq);
1591
1592 if (is_bare_jid) {
1593 if (jb->resources) {
1594 for(resources = jb->resources; resources; resources = resources->next) {
1595 JabberBuddyResource *jbr = resources->data;
1596 dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1597 }
1598 } else {
1599 /* user is offline, send a jabber:iq:last to find out last time online */
1600 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1601 xmlnode_set_attrib(iq->node, "to", jid);
1602 jabber_iq_set_callback(iq, jabber_last_offline_parse, jbi);
1603 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1604 jabber_iq_send(iq);
1605 }
1606 } else {
1607 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, slash + 1);
1608 if (jbr)
1609 dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1610 else
1611 purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1612 "was passed JID %s, but there is no corresponding "
1613 "JabberBuddyResource!\n", jid);
1614 }
1615
1616 js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi);
1617 jbi->timeout_handle = purple_timeout_add_seconds(30, jabber_buddy_get_info_timeout, jbi);
1618 }
1619
jabber_buddy_get_info(PurpleConnection * gc,const char * who)1620 void jabber_buddy_get_info(PurpleConnection *gc, const char *who)
1621 {
1622 JabberStream *js = purple_connection_get_protocol_data(gc);
1623 JabberID *jid = jabber_id_new(who);
1624
1625 if (!jid)
1626 return;
1627
1628 if (jid->node && jabber_chat_find(js, jid->node, jid->domain)) {
1629 /* For a conversation, include the resource (indicates the user). */
1630 jabber_buddy_get_info_for_jid(js, who);
1631 } else {
1632 char *bare_jid = jabber_get_bare_jid(who);
1633 jabber_buddy_get_info_for_jid(js, bare_jid);
1634 g_free(bare_jid);
1635 }
1636
1637 jabber_id_free(jid);
1638 }
1639
jabber_buddy_set_invisibility(JabberStream * js,const char * who,gboolean invisible)1640 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
1641 gboolean invisible)
1642 {
1643 PurplePresence *gpresence;
1644 PurpleAccount *account;
1645 PurpleStatus *status;
1646 JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
1647 xmlnode *presence;
1648 JabberBuddyState state;
1649 char *msg;
1650 int priority;
1651
1652 account = purple_connection_get_account(js->gc);
1653 gpresence = purple_account_get_presence(account);
1654 status = purple_presence_get_active_status(gpresence);
1655
1656 purple_status_to_jabber(status, &state, &msg, &priority);
1657 presence = jabber_presence_create_js(js, state, msg, priority);
1658
1659 g_free(msg);
1660
1661 xmlnode_set_attrib(presence, "to", who);
1662 if(invisible) {
1663 xmlnode_set_attrib(presence, "type", "invisible");
1664 jb->invisible |= JABBER_INVIS_BUDDY;
1665 } else {
1666 jb->invisible &= ~JABBER_INVIS_BUDDY;
1667 }
1668
1669 jabber_send(js, presence);
1670 xmlnode_free(presence);
1671 }
1672
jabber_buddy_make_invisible(PurpleBlistNode * node,gpointer data)1673 static void jabber_buddy_make_invisible(PurpleBlistNode *node, gpointer data)
1674 {
1675 PurpleBuddy *buddy;
1676 PurpleConnection *gc;
1677 JabberStream *js;
1678
1679 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1680
1681 buddy = (PurpleBuddy *) node;
1682 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1683 js = purple_connection_get_protocol_data(gc);
1684
1685 jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), TRUE);
1686 }
1687
jabber_buddy_make_visible(PurpleBlistNode * node,gpointer data)1688 static void jabber_buddy_make_visible(PurpleBlistNode *node, gpointer data)
1689 {
1690 PurpleBuddy *buddy;
1691 PurpleConnection *gc;
1692 JabberStream *js;
1693
1694 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1695
1696 buddy = (PurpleBuddy *) node;
1697 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1698 js = purple_connection_get_protocol_data(gc);
1699
1700 jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), FALSE);
1701 }
1702
cancel_presence_notification(gpointer data)1703 static void cancel_presence_notification(gpointer data)
1704 {
1705 PurpleBuddy *buddy;
1706 PurpleConnection *gc;
1707 JabberStream *js;
1708
1709 buddy = data;
1710 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1711 js = purple_connection_get_protocol_data(gc);
1712
1713 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribed");
1714 }
1715
1716 static void
jabber_buddy_cancel_presence_notification(PurpleBlistNode * node,gpointer data)1717 jabber_buddy_cancel_presence_notification(PurpleBlistNode *node,
1718 gpointer data)
1719 {
1720 PurpleBuddy *buddy;
1721 PurpleAccount *account;
1722 PurpleConnection *gc;
1723 const gchar *name;
1724 char *msg;
1725
1726 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1727
1728 buddy = (PurpleBuddy *) node;
1729 name = purple_buddy_get_name(buddy);
1730 account = purple_buddy_get_account(buddy);
1731 gc = purple_account_get_connection(account);
1732
1733 msg = g_strdup_printf(_("%s will no longer be able to see your status "
1734 "updates. Do you want to continue?"), name);
1735 purple_request_yes_no(gc, NULL, _("Cancel Presence Notification"),
1736 msg, 0 /* Yes */, account, name, NULL, buddy,
1737 cancel_presence_notification, NULL /* Do nothing */);
1738 g_free(msg);
1739 }
1740
jabber_buddy_rerequest_auth(PurpleBlistNode * node,gpointer data)1741 static void jabber_buddy_rerequest_auth(PurpleBlistNode *node, gpointer data)
1742 {
1743 PurpleBuddy *buddy;
1744 PurpleConnection *gc;
1745 JabberStream *js;
1746
1747 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1748
1749 buddy = (PurpleBuddy *) node;
1750 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1751 js = purple_connection_get_protocol_data(gc);
1752
1753 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "subscribe");
1754 }
1755
1756
jabber_buddy_unsubscribe(PurpleBlistNode * node,gpointer data)1757 static void jabber_buddy_unsubscribe(PurpleBlistNode *node, gpointer data)
1758 {
1759 PurpleBuddy *buddy;
1760 PurpleConnection *gc;
1761 JabberStream *js;
1762
1763 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1764
1765 buddy = (PurpleBuddy *) node;
1766 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1767 js = purple_connection_get_protocol_data(gc);
1768
1769 jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribe");
1770 }
1771
jabber_buddy_login(PurpleBlistNode * node,gpointer data)1772 static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
1773 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1774 /* simply create a directed presence of the current status */
1775 PurpleBuddy *buddy = (PurpleBuddy *) node;
1776 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1777 JabberStream *js = purple_connection_get_protocol_data(gc);
1778 PurpleAccount *account = purple_connection_get_account(gc);
1779 PurplePresence *gpresence = purple_account_get_presence(account);
1780 PurpleStatus *status = purple_presence_get_active_status(gpresence);
1781 xmlnode *presence;
1782 JabberBuddyState state;
1783 char *msg;
1784 int priority;
1785
1786 purple_status_to_jabber(status, &state, &msg, &priority);
1787 presence = jabber_presence_create_js(js, state, msg, priority);
1788
1789 g_free(msg);
1790
1791 xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1792
1793 jabber_send(js, presence);
1794 xmlnode_free(presence);
1795 }
1796 }
1797
jabber_buddy_logout(PurpleBlistNode * node,gpointer data)1798 static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
1799 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1800 /* simply create a directed unavailable presence */
1801 PurpleBuddy *buddy = (PurpleBuddy *) node;
1802 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1803 JabberStream *js = purple_connection_get_protocol_data(gc);
1804 xmlnode *presence;
1805
1806 presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
1807
1808 xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1809
1810 jabber_send(js, presence);
1811 xmlnode_free(presence);
1812 }
1813 }
1814
jabber_buddy_menu(PurpleBuddy * buddy)1815 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
1816 {
1817 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1818 JabberStream *js = purple_connection_get_protocol_data(gc);
1819 const char *name = purple_buddy_get_name(buddy);
1820 JabberBuddy *jb = jabber_buddy_find(js, name, TRUE);
1821 GList *jbrs;
1822
1823 GList *m = NULL;
1824 PurpleMenuAction *act;
1825
1826 if(!jb)
1827 return m;
1828
1829 if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
1830 jb != js->user_jb) {
1831 if(jb->invisible & JABBER_INVIS_BUDDY) {
1832 act = purple_menu_action_new(_("Un-hide From"),
1833 PURPLE_CALLBACK(jabber_buddy_make_visible),
1834 NULL, NULL);
1835 } else {
1836 act = purple_menu_action_new(_("Temporarily Hide From"),
1837 PURPLE_CALLBACK(jabber_buddy_make_invisible),
1838 NULL, NULL);
1839 }
1840 m = g_list_append(m, act);
1841 }
1842
1843 if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) {
1844 act = purple_menu_action_new(_("Cancel Presence Notification"),
1845 PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification),
1846 NULL, NULL);
1847 m = g_list_append(m, act);
1848 }
1849
1850 if(!(jb->subscription & JABBER_SUB_TO)) {
1851 act = purple_menu_action_new(_("(Re-)Request authorization"),
1852 PURPLE_CALLBACK(jabber_buddy_rerequest_auth),
1853 NULL, NULL);
1854 m = g_list_append(m, act);
1855
1856 } else if (jb != js->user_jb) {
1857
1858 /* shouldn't this just happen automatically when the buddy is
1859 removed? */
1860 act = purple_menu_action_new(_("Unsubscribe"),
1861 PURPLE_CALLBACK(jabber_buddy_unsubscribe),
1862 NULL, NULL);
1863 m = g_list_append(m, act);
1864 }
1865
1866 if (js->googletalk) {
1867 act = purple_menu_action_new(_("Initiate _Chat"),
1868 PURPLE_CALLBACK(google_buddy_node_chat),
1869 NULL, NULL);
1870 m = g_list_append(m, act);
1871 }
1872
1873 /*
1874 * This if-condition implements parts of XEP-0100: Gateway Interaction
1875 *
1876 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1877 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1878 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1879 * people don't tend to have a server or other service there.
1880 *
1881 * TODO: Use disco#info...
1882 */
1883 if (strchr(name, '@') == NULL) {
1884 act = purple_menu_action_new(_("Log In"),
1885 PURPLE_CALLBACK(jabber_buddy_login),
1886 NULL, NULL);
1887 m = g_list_append(m, act);
1888 act = purple_menu_action_new(_("Log Out"),
1889 PURPLE_CALLBACK(jabber_buddy_logout),
1890 NULL, NULL);
1891 m = g_list_append(m, act);
1892 }
1893
1894 /* add all ad hoc commands to the action menu */
1895 for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
1896 JabberBuddyResource *jbr = jbrs->data;
1897 GList *commands;
1898 if (!jbr->commands)
1899 continue;
1900 for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
1901 JabberAdHocCommands *cmd = commands->data;
1902 act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
1903 m = g_list_append(m, act);
1904 }
1905 }
1906
1907 return m;
1908 }
1909
1910 GList *
jabber_blist_node_menu(PurpleBlistNode * node)1911 jabber_blist_node_menu(PurpleBlistNode *node)
1912 {
1913 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1914 return jabber_buddy_menu((PurpleBuddy *) node);
1915 } else {
1916 return NULL;
1917 }
1918 }
1919
1920
user_search_result_add_buddy_cb(PurpleConnection * gc,GList * row,void * user_data)1921 static void user_search_result_add_buddy_cb(PurpleConnection *gc, GList *row, void *user_data)
1922 {
1923 /* XXX find out the jid */
1924 purple_blist_request_add_buddy(purple_connection_get_account(gc),
1925 g_list_nth_data(row, 0), NULL, NULL);
1926 }
1927
user_search_result_cb(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1928 static void user_search_result_cb(JabberStream *js, const char *from,
1929 JabberIqType type, const char *id,
1930 xmlnode *packet, gpointer data)
1931 {
1932 PurpleNotifySearchResults *results;
1933 PurpleNotifySearchColumn *column;
1934 xmlnode *x, *query, *item, *field;
1935
1936 /* XXX error checking? */
1937 if(!(query = xmlnode_get_child(packet, "query")))
1938 return;
1939
1940 results = purple_notify_searchresults_new();
1941 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1942 xmlnode *reported;
1943 GSList *column_vars = NULL;
1944
1945 purple_debug_info("jabber", "new-skool\n");
1946
1947 if((reported = xmlnode_get_child(x, "reported"))) {
1948 xmlnode *field = xmlnode_get_child(reported, "field");
1949 while(field) {
1950 const char *var = xmlnode_get_attrib(field, "var");
1951 const char *label = xmlnode_get_attrib(field, "label");
1952 if(var) {
1953 column = purple_notify_searchresults_column_new(label ? label : var);
1954 purple_notify_searchresults_column_add(results, column);
1955 column_vars = g_slist_append(column_vars, (char *)var);
1956 }
1957 field = xmlnode_get_next_twin(field);
1958 }
1959 }
1960
1961 item = xmlnode_get_child(x, "item");
1962 while(item) {
1963 GList *row = NULL;
1964 GSList *l;
1965 xmlnode *valuenode;
1966 const char *var;
1967
1968 for (l = column_vars; l != NULL; l = l->next) {
1969 /*
1970 * Build a row containing the strings that correspond
1971 * to each column of the search results.
1972 */
1973 for (field = xmlnode_get_child(item, "field");
1974 field != NULL;
1975 field = xmlnode_get_next_twin(field))
1976 {
1977 if ((var = xmlnode_get_attrib(field, "var")) &&
1978 purple_strequal(var, l->data) &&
1979 (valuenode = xmlnode_get_child(field, "value")))
1980 {
1981 char *value = xmlnode_get_data(valuenode);
1982 row = g_list_append(row, value);
1983 break;
1984 }
1985 }
1986 if (field == NULL)
1987 /* No data for this column */
1988 row = g_list_append(row, NULL);
1989 }
1990 purple_notify_searchresults_row_add(results, row);
1991 item = xmlnode_get_next_twin(item);
1992 }
1993
1994 g_slist_free(column_vars);
1995 } else {
1996 /* old skool */
1997 purple_debug_info("jabber", "old-skool\n");
1998
1999 column = purple_notify_searchresults_column_new(_("JID"));
2000 purple_notify_searchresults_column_add(results, column);
2001 column = purple_notify_searchresults_column_new(_("First Name"));
2002 purple_notify_searchresults_column_add(results, column);
2003 column = purple_notify_searchresults_column_new(_("Last Name"));
2004 purple_notify_searchresults_column_add(results, column);
2005 column = purple_notify_searchresults_column_new(_("Nickname"));
2006 purple_notify_searchresults_column_add(results, column);
2007 column = purple_notify_searchresults_column_new(_("Email"));
2008 purple_notify_searchresults_column_add(results, column);
2009
2010 for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) {
2011 const char *jid;
2012 xmlnode *node;
2013 GList *row = NULL;
2014
2015 if(!(jid = xmlnode_get_attrib(item, "jid")))
2016 continue;
2017
2018 row = g_list_append(row, g_strdup(jid));
2019 node = xmlnode_get_child(item, "first");
2020 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2021 node = xmlnode_get_child(item, "last");
2022 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2023 node = xmlnode_get_child(item, "nick");
2024 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2025 node = xmlnode_get_child(item, "email");
2026 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2027 purple_debug_info("jabber", "row=%p\n", row);
2028 purple_notify_searchresults_row_add(results, row);
2029 }
2030 }
2031
2032 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
2033 user_search_result_add_buddy_cb);
2034
2035 purple_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
2036 }
2037
user_search_x_data_cb(JabberStream * js,xmlnode * result,gpointer data)2038 static void user_search_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
2039 {
2040 xmlnode *query;
2041 JabberIq *iq;
2042 char *dir_server = data;
2043 const char *type;
2044
2045 /* if they've cancelled the search, we're
2046 * just going to get an error if we send
2047 * a cancel, so skip it */
2048 type = xmlnode_get_attrib(result, "type");
2049 if(purple_strequal(type, "cancel")) {
2050 g_free(dir_server);
2051 return;
2052 }
2053
2054 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2055 query = xmlnode_get_child(iq->node, "query");
2056
2057 xmlnode_insert_child(query, result);
2058
2059 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2060 xmlnode_set_attrib(iq->node, "to", dir_server);
2061 jabber_iq_send(iq);
2062 g_free(dir_server);
2063 }
2064
2065 struct user_search_info {
2066 JabberStream *js;
2067 char *directory_server;
2068 };
2069
user_search_cancel_cb(struct user_search_info * usi,PurpleRequestFields * fields)2070 static void user_search_cancel_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2071 {
2072 g_free(usi->directory_server);
2073 g_free(usi);
2074 }
2075
user_search_cb(struct user_search_info * usi,PurpleRequestFields * fields)2076 static void user_search_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2077 {
2078 JabberStream *js = usi->js;
2079 JabberIq *iq;
2080 xmlnode *query;
2081 GList *groups, *flds;
2082
2083 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2084 query = xmlnode_get_child(iq->node, "query");
2085
2086 for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
2087 for(flds = purple_request_field_group_get_fields(groups->data);
2088 flds; flds = flds->next) {
2089 PurpleRequestField *field = flds->data;
2090 const char *id = purple_request_field_get_id(field);
2091 const char *value = purple_request_field_string_get_value(field);
2092
2093 if(value && (purple_strequal(id, "first") || purple_strequal(id, "last") || purple_strequal(id, "nick") || purple_strequal(id, "email"))) {
2094 xmlnode *y = xmlnode_new_child(query, id);
2095 xmlnode_insert_data(y, value, -1);
2096 }
2097 }
2098 }
2099
2100 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2101 xmlnode_set_attrib(iq->node, "to", usi->directory_server);
2102 jabber_iq_send(iq);
2103
2104 g_free(usi->directory_server);
2105 g_free(usi);
2106 }
2107
2108 #if 0
2109 /* This is for gettext only -- it will see this even though there's an #if 0. */
2110
2111 /*
2112 * An incomplete list of server generated original language search
2113 * comments for Jabber User Directories
2114 *
2115 * See discussion thread "Search comment for Jabber is not translatable"
2116 * in purple-i18n@lists.sourceforge.net (March 2006)
2117 */
2118 static const char * jabber_user_dir_comments [] = {
2119 /* current comment from Jabber User Directory users.jabber.org */
2120 N_("Find a contact by entering the search criteria in the given fields. "
2121 "Note: Each field supports wild card searches (%)"),
2122 NULL
2123 };
2124 #endif
2125
user_search_fields_result_cb(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)2126 static void user_search_fields_result_cb(JabberStream *js, const char *from,
2127 JabberIqType type, const char *id,
2128 xmlnode *packet, gpointer data)
2129 {
2130 xmlnode *query, *x;
2131
2132 if (!from)
2133 return;
2134
2135 if (type == JABBER_IQ_ERROR) {
2136 char *msg = jabber_parse_error(js, packet, NULL);
2137
2138 if(!msg)
2139 msg = g_strdup(_("Unknown error"));
2140
2141 purple_notify_error(js->gc, _("Directory Query Failed"),
2142 _("Could not query the directory server."), msg);
2143 g_free(msg);
2144
2145 return;
2146 }
2147
2148
2149 if(!(query = xmlnode_get_child(packet, "query")))
2150 return;
2151
2152 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
2153 jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
2154 return;
2155 } else {
2156 struct user_search_info *usi;
2157 xmlnode *instnode;
2158 char *instructions = NULL;
2159 PurpleRequestFields *fields;
2160 PurpleRequestFieldGroup *group;
2161 PurpleRequestField *field;
2162
2163 /* old skool */
2164 fields = purple_request_fields_new();
2165 group = purple_request_field_group_new(NULL);
2166 purple_request_fields_add_group(fields, group);
2167
2168 if((instnode = xmlnode_get_child(query, "instructions")))
2169 {
2170 char *tmp = xmlnode_get_data(instnode);
2171
2172 if(tmp)
2173 {
2174 /* Try to translate the message (see static message
2175 list in jabber_user_dir_comments[]) */
2176 instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
2177 g_free(tmp);
2178 }
2179 }
2180
2181 if(!instructions)
2182 {
2183 instructions = g_strdup(_("Fill in one or more fields to search "
2184 "for any matching XMPP users."));
2185 }
2186
2187 if(xmlnode_get_child(query, "first")) {
2188 field = purple_request_field_string_new("first", _("First Name"),
2189 NULL, FALSE);
2190 purple_request_field_group_add_field(group, field);
2191 }
2192 if(xmlnode_get_child(query, "last")) {
2193 field = purple_request_field_string_new("last", _("Last Name"),
2194 NULL, FALSE);
2195 purple_request_field_group_add_field(group, field);
2196 }
2197 if(xmlnode_get_child(query, "nick")) {
2198 field = purple_request_field_string_new("nick", _("Nickname"),
2199 NULL, FALSE);
2200 purple_request_field_group_add_field(group, field);
2201 }
2202 if(xmlnode_get_child(query, "email")) {
2203 field = purple_request_field_string_new("email", _("Email Address"),
2204 NULL, FALSE);
2205 purple_request_field_group_add_field(group, field);
2206 }
2207
2208 usi = g_new0(struct user_search_info, 1);
2209 usi->js = js;
2210 usi->directory_server = g_strdup(from);
2211
2212 purple_request_fields(js->gc, _("Search for XMPP users"),
2213 _("Search for XMPP users"), instructions, fields,
2214 _("Search"), G_CALLBACK(user_search_cb),
2215 _("Cancel"), G_CALLBACK(user_search_cancel_cb),
2216 purple_connection_get_account(js->gc), NULL, NULL,
2217 usi);
2218
2219 g_free(instructions);
2220 }
2221 }
2222
jabber_user_search(JabberStream * js,const char * directory)2223 void jabber_user_search(JabberStream *js, const char *directory)
2224 {
2225 JabberIq *iq;
2226
2227 /* XXX: should probably better validate the directory we're given */
2228 if(!directory || !*directory) {
2229 purple_notify_error(js->gc, _("Invalid Directory"), _("Invalid Directory"), NULL);
2230 return;
2231 }
2232
2233 /* If the value provided isn't the disco#info default, persist it. Otherwise,
2234 make sure we aren't persisting an old value */
2235 if(js->user_directories && js->user_directories->data &&
2236 purple_strequal(directory, js->user_directories->data)) {
2237 purple_account_set_string(js->gc->account, "user_directory", "");
2238 }
2239 else {
2240 purple_account_set_string(js->gc->account, "user_directory", directory);
2241 }
2242
2243 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
2244 xmlnode_set_attrib(iq->node, "to", directory);
2245
2246 jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
2247
2248 jabber_iq_send(iq);
2249 }
2250
jabber_user_search_begin(PurplePluginAction * action)2251 void jabber_user_search_begin(PurplePluginAction *action)
2252 {
2253 PurpleConnection *gc = (PurpleConnection *) action->context;
2254 JabberStream *js = purple_connection_get_protocol_data(gc);
2255 const char *def_val = purple_account_get_string(js->gc->account, "user_directory", "");
2256 if(!*def_val && js->user_directories)
2257 def_val = js->user_directories->data;
2258
2259 purple_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
2260 _("Select a user directory to search"),
2261 def_val,
2262 FALSE, FALSE, NULL,
2263 _("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
2264 _("Cancel"), NULL,
2265 NULL, NULL, NULL,
2266 js);
2267 }
2268
2269 gboolean
jabber_resource_know_capabilities(const JabberBuddyResource * jbr)2270 jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
2271 {
2272 return jbr->caps.info != NULL;
2273 }
2274
2275 gboolean
jabber_resource_has_capability(const JabberBuddyResource * jbr,const gchar * cap)2276 jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
2277 {
2278 const GList *node = NULL;
2279 const JabberCapsNodeExts *exts;
2280
2281 if (!jbr->caps.info) {
2282 purple_debug_info("jabber",
2283 "Unable to find caps: nothing known about buddy\n");
2284 return FALSE;
2285 }
2286
2287 node = g_list_find_custom(jbr->caps.info->features, cap, (GCompareFunc)strcmp);
2288 if (!node && jbr->caps.exts && jbr->caps.info->exts) {
2289 const GList *ext;
2290 exts = jbr->caps.info->exts;
2291 /* Walk through all the enabled caps, checking each list for the cap.
2292 * Don't check it twice, though. */
2293 for (ext = jbr->caps.exts; ext && !node; ext = ext->next) {
2294 GList *features = g_hash_table_lookup(exts->exts, ext->data);
2295 if (features)
2296 node = g_list_find_custom(features, cap, (GCompareFunc)strcmp);
2297 }
2298 }
2299
2300 return (node != NULL);
2301 }
2302
2303 gboolean
jabber_buddy_has_capability(const JabberBuddy * jb,const gchar * cap)2304 jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap)
2305 {
2306 JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL);
2307
2308 if (!jbr) {
2309 purple_debug_info("jabber",
2310 "Unable to find caps: buddy might be offline\n");
2311 return FALSE;
2312 }
2313
2314 return jabber_resource_has_capability(jbr, cap);
2315 }
2316
2317 const gchar *
jabber_resource_get_identity_category_type(const JabberBuddyResource * jbr,const gchar * category)2318 jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
2319 const gchar *category)
2320 {
2321 const GList *iter = NULL;
2322
2323 if (jbr->caps.info) {
2324 for (iter = jbr->caps.info->identities ; iter ; iter = g_list_next(iter)) {
2325 const JabberIdentity *identity =
2326 (JabberIdentity *) iter->data;
2327
2328 if (purple_strequal(identity->category, category)) {
2329 return identity->type;
2330 }
2331 }
2332 }
2333
2334 return NULL;
2335 }
2336