aboutsummaryrefslogtreecommitdiff
path: root/src/contactlist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/contactlist.c')
-rw-r--r--src/contactlist.c901
1 files changed, 901 insertions, 0 deletions
diff --git a/src/contactlist.c b/src/contactlist.c
new file mode 100644
index 0000000..03e2a53
--- /dev/null
+++ b/src/contactlist.c
@@ -0,0 +1,901 @@
+/*
+ * contactlist.c
+ *
+ * Contact list (FL, BL, AL and RL) data structures
+ *
+ * (c) 2002-2006 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 dated June, 1991.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "debug.h"
+#include "contactlist.h"
+#include "mainwindow.h"
+#include "msngenerics.h"
+#include "sbsessions.h"
+#include "avatars.h"
+#include "xml.h"
+
+/* The record format for a contact. The same record can be referenced from more than one list. Opaque. */
+typedef struct {
+
+ char *username; /* Username */
+ char *friendlyname; /* Friendlyname */
+ OnlineState status; /* Online status */
+ unsigned int features; /* Client features (e.g. webcam) */
+ char *dpobject; /* Display Picture MSNObject */
+ ContactSource source; /* The last source the details were updated from */
+ int references; /* Number of times this contact is referenced by ContactItems */
+ UIContact *uicontact; /* src/mainwindow.c's representation of the contact */
+ char *dpsha1d; /* SHA1D field (if any) from user's DP MSNObject (if any). */
+ char *ubxdata; /* UBX data for this contact. */
+ size_t ubxdatalen; /* Length of UBX data. */
+ char *guid; /* Contact's GUID */
+
+} Contact;
+/* ... but ContactItems actually appear on the lists ... */
+
+typedef struct contactitem {
+
+ Contact *contact; /* The contact's record */
+ struct contactitem *next; /* Link to the next contact on this list */
+
+} ContactItem;
+
+static ContactItem *contactlist_forward = NULL;
+static ContactItem *contactlist_block = NULL;
+static ContactItem *contactlist_allow = NULL;
+static ContactItem *contactlist_reverse = NULL;
+static ContactItem *contactlist_pending = NULL;
+static ContactItem *contactlist_temporary = NULL;
+
+static ContactItem *contactlist_lastcontactitem(ContactItem *contact_list) {
+
+ ContactItem *contactitem = contact_list;
+
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ if ( contactitem->next == NULL ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ /* Should never get here */
+ debug_print("CL: Reached end of contactlist_lastcontactitem(). D'oh.\n");
+ return contactitem;
+
+}
+
+
+static ContactItem **contactlist_translatelist(const char *list) {
+
+ if ( strcmp(list, "AL") == 0 ) {
+ return &contactlist_allow;
+ } else if ( strcmp(list, "FL") == 0 ) {
+ return &contactlist_forward;
+ } else if ( strcmp(list, "BL") == 0 ) {
+ return &contactlist_block;
+ } else if ( strcmp(list, "RL") == 0 ) {
+ return &contactlist_reverse;
+ } else if ( strcmp(list, "PL") == 0 ) {
+ return &contactlist_pending;
+ } else {
+ return NULL;
+ }
+
+}
+
+/* Link a contact into a given contact list, creating the ContactItem structure */
+static void contactlist_link(ContactItem **contact_list, Contact *contact) {
+
+ ContactItem *last_contactitem;
+ ContactItem *new_contactitem;
+
+ new_contactitem = malloc(sizeof(ContactItem));
+ new_contactitem->contact = contact;
+ new_contactitem->next = NULL;
+
+ last_contactitem = contactlist_lastcontactitem(*contact_list);
+ if ( last_contactitem != NULL ) {
+ assert(last_contactitem->next == NULL);
+ last_contactitem->next = new_contactitem;
+ } else {
+ *contact_list = new_contactitem;;
+ }
+
+ contact->references++;
+
+}
+
+static ContactItem *contactlist_previous(ContactItem **contact_list, ContactItem *s_contactitem) {
+
+ ContactItem *contactitem;
+
+ contactitem = *contact_list;
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ if ( contactitem->next == s_contactitem ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ /* Didn't find it - means it was the first item on the list. */
+ return NULL;
+
+}
+
+/* Unlink a contact from a given contact list, destroying it if there's no more references */
+static void contactlist_unlink(ContactItem **contact_list, ContactItem *contactitem) {
+
+ Contact *contact;
+ ContactItem *previous_contact;
+ ContactItem *next_contact;
+
+ contact = contactitem->contact;
+
+/* debug_print("CL: Unlinking %s\n", contact->username);*/
+
+ /* First link it out of the list. */
+ previous_contact = contactlist_previous(contact_list, contactitem);
+ next_contact = contactitem->next;
+ if ( previous_contact != NULL ) {
+ previous_contact->next = next_contact;
+ } else {
+ *contact_list = next_contact;
+ }
+ contact->references--;
+/* debug_print("CL: Reference count now %i\n", contact->references);*/
+
+ if ( (contact_list == &contactlist_forward) && (contact->uicontact != NULL) ) {
+ /* This has probably already happened. */
+ debug_print("CL: Destroying UIContact\n");
+ mainwindow_removecontact(contact->uicontact);
+ mainwindow_destroycontact(contact->uicontact);
+ }
+
+ if ( contact->references == 0 ) {
+
+ debug_print("CL: Destroying contact record for %s\n", contact->username);
+
+ /* Destroy the contact record */
+ free(contact->username);
+ if ( contact->friendlyname != NULL ) {
+ free(contact->friendlyname);
+ }
+ if ( contact->dpobject != NULL ) {
+ free(contact->dpobject);
+ }
+ if ( contact->dpsha1d != NULL ) {
+ free(contact->dpsha1d);
+ }
+ if ( contact->ubxdata != NULL ) {
+ free(contact->ubxdata);
+ }
+ if ( contact->guid != NULL ) {
+ free(contact->guid);
+ }
+ free(contact);
+
+ }
+
+ free(contactitem);
+
+}
+
+/* Locate a contact's record in a given list by username */
+static ContactItem *contactlist_findcontact(ContactItem *contact_list, const char *username) {
+
+ ContactItem *contactitem = contact_list;
+
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ /* Case-insensitive here, like in sbsessions_find_username(). Username
+ may have different case depending on its source, since the servers
+ seem to change usernames to lower case but clients might not in
+ (e.g.) TypingUser controls. */
+ if ( strcasecmp(contactitem->contact->username, username) == 0 ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ return NULL;
+
+}
+
+/* Locate a contact's record in a given list by GUID */
+static ContactItem *contactlist_findcontactguid(ContactItem *contact_list, const char *guid) {
+
+ ContactItem *contactitem = contact_list;
+
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->guid != NULL);
+
+ /* Case-insensitive */
+ if ( strcasecmp(contactitem->contact->guid, guid) == 0 ) {
+ return contactitem;
+ } else {
+ contactitem = contactitem->next;
+ }
+
+ }
+
+ return NULL;
+
+}
+
+void contactlist_removecontact(const char *list, const char *username) {
+
+ ContactItem **contact_list = contactlist_translatelist(list);
+ ContactItem *contactitem;
+
+ debug_print("CL: Removing %s from %s\n", username, list);
+
+ contactitem = contactlist_findcontact(*contact_list, username);
+ assert(contactitem != NULL);
+ contactlist_unlink(contact_list, contactitem);
+
+}
+
+void contactlist_removecontactguid(const char *list, const char *guid) {
+
+ ContactItem **contact_list = contactlist_translatelist(list);
+ ContactItem *contactitem;
+
+ debug_print("CL: Removing %s from %s\n", guid, list);
+
+ contactitem = contactlist_findcontactguid(*contact_list, guid);
+ assert(contactitem != NULL);
+ contactlist_unlink(contact_list, contactitem);
+
+}
+
+static Contact *contactlist_findcontactrecord(ContactItem *contact_list, const char *username) {
+
+ ContactItem *contactitem;
+
+ contactitem = contactlist_findcontact(contact_list, username);
+ if ( contactitem == NULL ) {
+ return NULL;
+ }
+
+ return contactitem->contact;
+
+}
+
+/* Create a new contact record */
+static Contact *contactlist_createcontact(ContactSource source, const char *username, const char *friendlyname, OnlineState status, int features, const char *dpobject, const char *guid) {
+
+ Contact *newcontact;
+
+ newcontact = malloc(sizeof(Contact));
+ assert(newcontact != NULL);
+
+ newcontact->username = strdup(username);
+ if ( friendlyname != NULL ) {
+ newcontact->friendlyname = strdup(friendlyname);
+ } else {
+ newcontact->friendlyname = NULL;
+ }
+ if ( dpobject != NULL ) {
+ newcontact->dpobject = strdup(dpobject);
+ } else {
+ newcontact->dpobject = NULL; /* Don't try to strdup(NULL) */
+ }
+ if ( guid != NULL ) {
+ newcontact->guid = strdup(guid);
+ } else {
+ newcontact->guid = NULL;
+ }
+ newcontact->source = source;
+ newcontact->uicontact = NULL; /* This is filled in if/when the contact is linked into the FL */
+ newcontact->features = features;
+ newcontact->status = status;
+ newcontact->references = 0;
+ newcontact->dpsha1d = NULL;
+ newcontact->ubxdata = NULL;
+
+ /* Caller probably means to call contactlist_link pretty soon... */
+ return newcontact;
+
+}
+
+/* Add a user to the a given list or update details if already there */
+static void contactlist_details(ContactItem **contact_list, ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid) {
+
+ ContactItem *contact_item;
+ Contact *contact;
+ char *list_string;
+
+ assert(username != NULL);
+ assert(contact_list != NULL); /* But *contact_list is allowed to be NULL (empty list) */
+
+ if ( contact_list == &contactlist_allow ) {
+ list_string = "AL";
+ } else if ( contact_list == &contactlist_forward ) {
+ list_string = "FL";
+ } else if ( contact_list == &contactlist_block ) {
+ list_string = "BL";
+ } else if ( contact_list == &contactlist_reverse ) {
+ list_string = "RL";
+ } else if ( contact_list == &contactlist_temporary ) {
+ list_string = "TL";
+ } else if ( contact_list == &contactlist_pending ) {
+ list_string = "PL";
+ } else {
+ list_string = "??"; /* Whoops */
+ }
+
+ /* Look in all the lists to try and find the record for this user */
+ contact_item = contactlist_findcontact(contactlist_forward, username);
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_reverse, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_allow, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_block, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_temporary, username);
+ }
+ if ( contact_item == NULL ) {
+ contact_item = contactlist_findcontact(contactlist_pending, username);
+ }
+
+ /* If they're not found, create them. */
+ if ( contact_item == NULL ) {
+
+ debug_print("CL: Creating contact record: %s / %s\n", username, friendlyname);
+ contact = contactlist_createcontact(source, username, friendlyname, status, features, dpobject, guid);
+ assert(contact != NULL); /* Shouldn't ever happen */
+
+ } else {
+
+ /* Found the contact, so check if they need to be updated */
+ contact = contact_item->contact;
+ assert(contact != NULL);
+
+ if ( contact->source <= source ) {
+
+ int dpchanged = 0;
+
+ debug_print("CL: Updating record: %s / %s\n", username, friendlyname);
+
+ /* Don't change friendlyname non-NULL to NULL */
+ if ( !((contact->friendlyname != NULL) && (friendlyname == NULL)) ) {
+ if ( contact->friendlyname != NULL ) {
+ free(contact->friendlyname);
+ }
+ if ( friendlyname != NULL ) {
+ contact->friendlyname = strdup(friendlyname);
+ } else {
+ contact->friendlyname = NULL;
+ }
+ }
+
+ /* Don't change GUID non-NULL to NULL either. In fact, this shouldn't be changing
+ anyway, but I'm not willing to assume that. */
+ if ( !((contact->guid != NULL) && (guid == NULL)) ) {
+ if ( contact->guid != NULL ) {
+ free(contact->guid);
+ }
+ if ( guid != NULL ) {
+ contact->guid = strdup(guid);
+ } else {
+ contact->guid = NULL;
+ }
+ }
+
+ /* This detects DP->no DP and No DP->DP transitions. */
+ if ( contact->dpobject != dpobject ) {
+ dpchanged = 1;
+ }
+ /* This detects DP->DP transitions and invalidates same-DP changes. */
+ if ( contact->dpobject != NULL ) {
+
+ if ( dpobject != NULL ) {
+
+ if ( strcmp(dpobject, contact->dpobject) != 0 ) {
+ dpchanged = 1;
+ } else {
+ dpchanged = 0;
+ }
+
+ }
+ free(contact->dpobject);
+ if ( contact->dpsha1d != NULL ) {
+ free(contact->dpsha1d);
+ }
+
+ }
+
+ contact->features = features;
+ if ( dpobject != NULL ) {
+ contact->dpobject = strdup(dpobject);
+ contact->dpsha1d = avatars_unwrapsha1d(dpobject);
+ } else {
+ contact->dpobject = NULL;
+ contact->dpsha1d = NULL;
+ }
+
+ if ( dpchanged ) {
+
+ /* Change of DP */
+ debug_print("CL: Display picture for %s changed.\n", username);
+ messagewindow_picturekick(username);
+
+ }
+
+ contact->source = source;
+ contact->status = status;
+
+ }
+
+ }
+
+ /* Check if they're in the right list. If not, link them into it. */
+ if ( contactlist_findcontact(*contact_list, username) == NULL ) {
+
+ assert(contact != NULL);
+ assert(contact->username != NULL);
+ debug_print("CL: Linking into %s: %s\n", list_string, contact->username);
+
+ contactlist_link(contact_list, contact);
+
+ if ( contact_list == &contactlist_forward ) {
+ /* Linking into the FL so create an UIContact for them. */
+ if ( contact->uicontact == NULL ) {
+ contact->uicontact = mainwindow_addcontact(contact->username, contact->friendlyname, status);
+ }
+ }
+
+ }
+
+ /* If an NLN, ILN or FLN (for the FL) is BEING HANDLED, and the details have been updated, sort the UI out.
+ N.B. What's being handled doesn't necessarily have anything to do with the contact's status. */
+ if ( ((source == CONTACT_SOURCE_NLN) || (source == CONTACT_SOURCE_FLN)) && (contact_list == &contactlist_forward) ) {
+ assert(contact->uicontact != NULL);
+ mainwindow_setcontactstatus(contact->uicontact, contact->username, contact->friendlyname, status);
+ }
+
+}
+
+/* Add a user to the FL or update details if already there */
+void contactlist_fldetails(ContactSource source, const char *username, const char *friendlyname, int features, const char *dpobject, OnlineState status, const char *guid) {
+ contactlist_details(&contactlist_forward, source, username, friendlyname, features, dpobject, status, guid);
+}
+
+/* Add a user to the AL or update details if already there */
+void contactlist_aldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_allow, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the BL or update details if already there */
+void contactlist_bldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_block, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the RL or update details if already there */
+void contactlist_rldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_reverse, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the TL (temporary list) */
+void contactlist_tldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_temporary, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Add a user to the PL (pending list) */
+void contactlist_pldetails(ContactSource source, const char *username, const char *friendlyname, OnlineState status) {
+ contactlist_details(&contactlist_pending, source, username, friendlyname, 0, NULL, status, NULL);
+}
+
+/* Do the same as messagewindow_picturekick, but identify "kickees" by DP SHA1D field. */
+void contactlist_picturekick_sha1d(const char *sha1d) {
+
+ /* Find all users who have this DP SHA1D */
+ ContactItem *contactitem = contactlist_forward;
+ if ( contactitem == NULL ) {
+ return;
+ }
+
+ while ( contactitem ) {
+
+ assert(contactitem->contact != NULL);
+ if ( contactitem->contact->dpsha1d != NULL ) {
+ if ( strcmp(contactitem->contact->dpsha1d , sha1d) == 0 ) {
+ messagewindow_picturekick(contactitem->contact->username);
+ }
+ }
+
+ contactitem = contactitem->next;
+
+ }
+
+}
+
+static void contactlist_clear_list(ContactItem **contact_list) {
+
+ ContactItem *contactitem = *contact_list;
+
+ /* Check the list isn't empty first. */
+ if ( contactitem == NULL ) {
+ debug_print("CL: List is empty\n");
+ return;
+ }
+
+ while ( contactitem ) {
+
+ ContactItem *next_contactitem = NULL;
+
+ assert(contactitem != NULL);
+ assert(contactitem->contact != NULL);
+ assert(contactitem->contact->username != NULL);
+
+ next_contactitem = contactitem->next;
+
+ contactlist_unlink(contact_list, contactitem);
+ contactitem = next_contactitem;
+
+ }
+
+}
+
+int contactlist_isonlist(const char *list, const char *username) {
+
+ ContactItem **contact_list = contactlist_translatelist(list);
+
+ if ( contactlist_findcontact(*contact_list, username) == NULL ) {
+ return 0;
+ }
+
+ return 1;
+
+}
+
+/* Called by src/msnprotocol.c to wipe the contact list at sign-out, and by src/listcache.c to invalidate the lists. */
+void contactlist_clear() {
+
+ debug_print("CL: Clearing FL\n");
+ contactlist_clear_list(&contactlist_forward);
+ debug_print("CL: Clearing RL\n");
+ contactlist_clear_list(&contactlist_reverse);
+ debug_print("CL: Clearing BL\n");
+ contactlist_clear_list(&contactlist_block);
+ debug_print("CL: Clearing AL\n");
+ contactlist_clear_list(&contactlist_allow);
+ debug_print("CL: Clearing TL\n");
+ contactlist_clear_list(&contactlist_temporary);
+ debug_print("CL: Clearing PL\n");
+ contactlist_clear_list(&contactlist_pending);
+
+ /* Check */
+ assert(contactlist_forward == NULL);
+ assert(contactlist_block == NULL);
+ assert(contactlist_allow == NULL);
+ assert(contactlist_reverse == NULL);
+ assert(contactlist_temporary == NULL);
+
+}
+
+/* Return contact data as a string. */
+static char *contactlist_getcontact(const char *list, ContactItem **item) {
+
+ char *r_contact;
+ size_t length;
+
+ if ( (*item == NULL) && (strcmp(list, "FL") == 0) ) {
+ *item = contactlist_forward;
+ } else if ( (*item == NULL) && (strcmp(list, "AL") == 0) ) {
+ *item = contactlist_allow;
+ } else if ( (*item == NULL) && (strcmp(list, "BL") == 0) ) {
+ *item = contactlist_block;
+ } else if ( (*item == NULL) && (strcmp(list, "RL") == 0) ) {
+ *item = contactlist_reverse;
+ } else if ( (*item == NULL) && (strcmp(list, "PL") == 0) ) {
+ *item = contactlist_pending;
+ } else {
+ assert(item != NULL);
+ assert(*item != NULL);
+ *item = (*item)->next;
+ }
+
+ if ( *item == NULL ) {
+ return NULL;
+ }
+
+ length = strlen((*item)->contact->username) + 1;
+ if ( (*item)->contact->friendlyname != NULL ) {
+ length += strlen((*item)->contact->friendlyname) + 1;
+ }
+ if ( (*item)->contact->guid != NULL ) {
+ length += strlen((*item)->contact->guid) + 1;
+ if ( (*item)->contact->friendlyname == NULL ) {
+ /* Whoops! GUID but no friendly name. */
+ length += 2;
+ }
+ }
+ r_contact = malloc(length);
+
+ strcpy(r_contact, (*item)->contact->username);
+ if ( (*item)->contact->friendlyname != NULL ) {
+ strcat(r_contact, " ");
+ strcat(r_contact, (*item)->contact->friendlyname);
+ }
+ if ( (*item)->contact->guid != NULL ) {
+ if ( (*item)->contact->friendlyname == NULL ) {
+ /* Shouldn't ever happen. Nasty situation. */
+ strcat(r_contact, " -");
+ }
+ strcat(r_contact, " ");
+ strcat(r_contact, (*item)->contact->guid);
+ }
+
+ return r_contact;
+
+}
+
+/* Tweak line ending before sending a line to a file. */
+static int contactlist_writeout(FILE *fh, const char *list, const char *contact) {
+
+ char *line;
+ int rval;
+
+ line = malloc(strlen(list) + strlen(contact) + 3);
+ strcpy(line, list);
+ strcat(line, " ");
+ strcat(line, contact);
+ line[strlen(line)+1] = '\0';
+ line[strlen(line)] = '\n';
+
+ rval = fputs(line, fh);
+ free(line);
+
+ if ( rval == EOF ) {
+ return -1;
+ }
+
+ return 0;
+
+}
+
+/* Dump a given list into the file handle given. */
+int contactlist_dumplist(FILE *fh, const char *list) {
+
+ ContactItem *token;
+ char *contact;
+
+ token = NULL;
+ contact = contactlist_getcontact(list, &token);
+ while ( token ) {
+
+ if ( contactlist_writeout(fh, list, contact) != 0 ) {
+ free(contact);
+ return -1;
+ }
+ free(contact);
+ contact = contactlist_getcontact(list, &token);
+
+ }
+
+ return 0;
+
+}
+
+const char *contactlist_friendlyname(const char *username) {
+
+ ContactItem *contact;
+
+ contact = contactlist_findcontact(contactlist_forward, username);
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_reverse, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_allow, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_block, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_temporary, username);
+ }
+ if ( contact == NULL ) {
+ contact = contactlist_findcontact(contactlist_pending, username);
+ }
+
+ /* Tricky one to handle. User isn't in any of the contact lists. Caller will probably create a
+ * record in the temporary list (which isn't stored on the server or in the cache) and use
+ * that as the SPOT. */
+ if ( contact == NULL ) {
+ debug_print("CL: contactlist_friendlyname: couldn't find user data.\n");
+ return NULL;
+ }
+
+ assert(contact != NULL);
+ assert(contact->contact != NULL);
+
+ /* Don't try to free this! You may also want to urldecode it. */
+ return contact->contact->friendlyname;
+
+}
+
+/* Check if a user has a display picture or not. Return NULL or an MSNObject as appropriate. */
+const char *contactlist_haspicture(const char *username) {
+
+ Contact *contact;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ if ( contact == NULL ) {
+ return NULL; /* e.g. if user was only on TL */
+ }
+ if ( (contact->dpobject != NULL) && (strlen(contact->dpobject) > 0) ) {
+ return contact->dpobject;
+ }
+
+ return NULL;
+
+}
+
+/* Return a contact's DP SHA1D, if they have one.. */
+const char *contactlist_dpsha1d(const char *username) {
+
+ Contact *contact;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ if ( contact != NULL ) {
+ return NULL;
+ }
+
+ if ( contact->dpsha1d != NULL ) {
+ return contact->dpsha1d;
+ }
+
+ return NULL;
+
+}
+
+/* Set UBX data for a contact. */
+void contactlist_setubx(const char *username, const char *ubxdata, size_t ubxdatalen) {
+
+ Contact *contact;
+ char *newubx;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ g_return_if_fail(contact != NULL);
+
+ if ( contact->ubxdata != NULL ) {
+ free(contact->ubxdata);
+ }
+
+ newubx = malloc(ubxdatalen);
+ memcpy(newubx, ubxdata, ubxdatalen);
+ contact->ubxdata = newubx;
+ contact->ubxdatalen = ubxdatalen;
+
+ /* Kick the UI */
+ debug_print("CL: Kicking UIContact because of UBX change.\n");
+ mainwindow_setcontactstatus(contact->uicontact, contact->username, contact->friendlyname, contact->status);
+
+}
+
+/* Return the Customised Status Message for a contact. */
+char *contactlist_csm(const char *username, int replace_entities) {
+
+ Contact *contact;
+
+ contact = contactlist_findcontactrecord(contactlist_forward, username);
+ if ( contact == NULL ) {
+ return NULL;
+ }
+ if ( contact->ubxdata == NULL ) {
+ return NULL;
+ }
+
+ /* Don't translate entities - Pango will do that later. */
+ return xml_getblock(contact->ubxdata, contact->ubxdatalen, "Data", "PSM", replace_entities);
+
+}
+
+ContactListIter *contactlist_iter_new() {
+
+ ContactListIter *iter = malloc(sizeof(ContactListIter));
+ *iter = 0;
+
+ return iter;
+
+}
+
+void contactlist_iter_destroy(ContactListIter *iter) {
+ free(iter);
+}
+
+const char *contactlist_getusername(ContactListIter *iter, const char *listl) {
+
+ unsigned int i;
+ ContactItem **list = contactlist_translatelist(listl);
+ ContactItem *item = *list;
+
+ if ( list == NULL ) {
+ debug_print("CL: contactlist_getusername: invalid list.\n");
+ return NULL;
+ }
+
+ for ( i=0; i<*iter; i++ ) {
+ if ( item == NULL ) {
+ break;
+ }
+ item = item->next;
+ }
+
+ if ( item == NULL ) {
+ return NULL;
+ }
+
+ (*iter)++;
+ return item->contact->username;
+
+}
+
+int contactlist_msnc(const char *username) {
+}