Coverage Report

Created: 2026-09-14 06:25

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