/* * contactlist.c * * Contact list (FL, BL, AL and RL) data structures * * (c) 2002-2006 Thomas White * 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 #endif #include #include #include #include #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) { }