/* * * OBEX Server * * Copyright (C) 2009-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * * This program 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; either version 2 of the License, or * (at your option) any later version. * * This program 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "log.h" #include "obex.h" #include "service.h" #include "phonebook.h" #define QUERY_FN "(contains \"family_name\" \"%s\")" #define QUERY_NAME "(contains \"given_name\" \"%s\")" #define QUERY_PHONE "(contains \"phone\" \"%s\")" struct query_context { const struct apparam_field *params; phonebook_cb contacts_cb; phonebook_entry_cb entry_cb; phonebook_cache_ready_cb ready_cb; EBookQuery *query; unsigned int count; GString *buf; char *id; unsigned queued_calls; void *user_data; GSList *ebooks; gboolean canceled; }; static char *attribute_mask[] = { /* 0 */ "VERSION", "FN", "N", "PHOTO", "BDAY", "ADR", "LABEL", "TEL", /* 8 */ "EMAIL", "MAILER", "TZ", "GEO", "TITLE", "ROLE", "LOGO", "AGENT", /* 16 */ "ORG", "NOTE", "REV", "SOUND", "URL", "UID", "KEY", "NICKNAME", /* 24 */ "CATEGORIES", "PROID", "CLASS", "SORT-STRING", /* 28 */ "X-IRMC-CALL-DATETIME", NULL }; static void close_ebooks(GSList *ebooks) { g_slist_free_full(ebooks, g_object_unref); } static void free_query_context(struct query_context *data) { g_free(data->id); if (data->buf != NULL) g_string_free(data->buf, TRUE); if (data->query != NULL) e_book_query_unref(data->query); close_ebooks(data->ebooks); g_free(data); } static char *evcard_to_string(EVCard *evcard, unsigned int format, uint64_t filter) { EVCard *evcard2; GList *l; char *vcard; if (!filter) return e_vcard_to_string(evcard, EVC_FORMAT_VCARD_30); /* XXX There is no support for VCARD 2.1 at this time */ /* * Mandatory attributes for vCard 2.1 are VERSION ,N and TEL. * Mandatory attributes for vCard 3.0 are VERSION, N, FN and TEL */ filter = format == EVC_FORMAT_VCARD_30 ? filter | 0x87: filter | 0x85; l = e_vcard_get_attributes(evcard); evcard2 = e_vcard_new(); for (; l; l = g_list_next(l)) { EVCardAttribute *attrib = l->data; const char *name; int i; if (!attrib) continue; name = e_vcard_attribute_get_name(attrib); for (i = 0; attribute_mask[i] != NULL; i++) { if (!(filter & (1 << i))) continue; if (g_strcmp0(name, attribute_mask[i]) != 0) continue; e_vcard_add_attribute(evcard2, e_vcard_attribute_copy(attrib)); } } vcard = e_vcard_to_string(evcard2, format); g_object_unref(evcard2); return vcard; } static void ebookpull_cb(EBook *book, const GError *gerr, GList *contacts, void *user_data) { struct query_context *data = user_data; GList *l; unsigned int count, maxcount; data->queued_calls--; if (data->canceled) goto canceled; if (gerr != NULL) { error("E-Book query failed: %s", gerr->message); goto done; } DBG(""); /* * When MaxListCount is zero, PCE wants to know the number of used * indexes in the phonebook of interest. All other parameters that * may be present in the request shall be ignored. */ maxcount = data->params->maxlistcount; if (maxcount == 0) { data->count += g_list_length(contacts); goto done; } l = g_list_nth(contacts, data->params->liststartoffset); for (count = 0; l && count + data->count < maxcount; l = g_list_next(l), count++) { EContact *contact = E_CONTACT(l->data); EVCard *evcard = E_VCARD(contact); char *vcard; vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30, data->params->filter); data->buf = g_string_append(data->buf, vcard); data->buf = g_string_append(data->buf, "\r\n"); g_free(vcard); } DBG("collected %d vcards", count); data->count += count; g_list_free_full(contacts, g_object_unref); done: if (data->queued_calls == 0) { GString *buf = data->buf; data->buf = NULL; data->contacts_cb(buf->str, buf->len, data->count, 0, TRUE, data->user_data); g_string_free(buf, TRUE); } return; canceled: if (data->queued_calls == 0) free_query_context(data); } static void ebook_entry_cb(EBook *book, const GError *gerr, EContact *contact, void *user_data) { struct query_context *data = user_data; EVCard *evcard; char *vcard; size_t len; data->queued_calls--; if (data->canceled) goto done; if (gerr != NULL) { error("E-Book query failed: %s", gerr->message); goto done; } DBG(""); evcard = E_VCARD(contact); vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30, data->params->filter); len = vcard ? strlen(vcard) : 0; data->count++; data->contacts_cb(vcard, len, 1, 0, TRUE, data->user_data); g_free(vcard); g_object_unref(contact); return; done: if (data->queued_calls == 0) { if (data->count == 0) data->contacts_cb(NULL, 0, 1, 0, TRUE, data->user_data); else if (data->canceled) free_query_context(data); } } static char *evcard_name_attribute_to_string(EVCard *evcard) { EVCardAttribute *attrib; GList *l; GString *name = NULL; attrib = e_vcard_get_attribute(evcard, EVC_N); if (!attrib) return NULL; for (l = e_vcard_attribute_get_values(attrib); l; l = l->next) { const char *value = l->data; if (!strlen(value)) continue; if (!name) name = g_string_new(value); else { name = g_string_append(name, ";"); name = g_string_append(name, l->data); } } if (!name) return NULL; return g_string_free(name, FALSE); } static void cache_cb(EBook *book, const GError *gerr, GList *contacts, void *user_data) { struct query_context *data = user_data; GList *l; data->queued_calls--; if (data->canceled) goto canceled; if (gerr != NULL) { error("E-Book operation failed: %s", gerr->message); goto done; } DBG(""); for (l = contacts; l; l = g_list_next(l)) { EContact *contact = E_CONTACT(l->data); EVCard *evcard = E_VCARD(contact); EVCardAttribute *attrib; char *uid, *tel, *name; name = evcard_name_attribute_to_string(evcard); if (!name) continue; attrib = e_vcard_get_attribute(evcard, EVC_UID); if (!attrib) continue; uid = e_vcard_attribute_get_value(attrib); if (!uid) continue; attrib = e_vcard_get_attribute(evcard, EVC_TEL); if (attrib) tel = e_vcard_attribute_get_value(attrib); else tel = g_strdup(""); data->entry_cb(uid, PHONEBOOK_INVALID_HANDLE, name, NULL, tel, data->user_data); g_free(name); g_free(uid); g_free(tel); } g_list_free_full(contacts, g_object_unref); done: if (data->queued_calls == 0) data->ready_cb(data->user_data); return; canceled: if (data->queued_calls == 0) free_query_context(data); } static GSList *traverse_sources(GSList *ebooks, GSList *sources, char **default_src) { GError *gerr = NULL; for (; sources != NULL; sources = g_slist_next(sources)) { char *uri; ESource *source = E_SOURCE(sources->data); EBook *ebook = e_book_new(source, &gerr); if (ebook == NULL) { error("Can't create user's address book: %s", gerr->message); g_clear_error(&gerr); continue; } uri = e_source_get_uri(source); if (g_strcmp0(*default_src, uri) == 0) { g_free(uri); continue; } g_free(uri); if (e_book_open(ebook, FALSE, &gerr) == FALSE) { error("Can't open e-book address book: %s", gerr->message); g_object_unref(ebook); g_clear_error(&gerr); continue; } if (*default_src == NULL) *default_src = e_source_get_uri(source); DBG("%s address book opened", e_source_peek_name(source)); ebooks = g_slist_append(ebooks, ebook); } return ebooks; } int phonebook_init(void) { g_type_init(); return 0; } static GSList *open_ebooks(void) { GError *gerr = NULL; ESourceList *src_list; GSList *list; char *default_src = NULL; GSList *ebooks = NULL; if (e_book_get_addressbooks(&src_list, &gerr) == FALSE) { error("Can't list user's address books: %s", gerr->message); g_error_free(gerr); return NULL; } list = e_source_list_peek_groups(src_list); while (list != NULL) { ESourceGroup *group = E_SOURCE_GROUP(list->data); GSList *sources = e_source_group_peek_sources(group); ebooks = traverse_sources(ebooks, sources, &default_src); list = list->next; } g_free(default_src); g_object_unref(src_list); return ebooks; } void phonebook_exit(void) { } char *phonebook_set_folder(const char *current_folder, const char *new_folder, uint8_t flags, int *err) { gboolean root, child; char *fullname = NULL, *tmp1, *tmp2, *base; int ret = 0, len; root = (g_strcmp0("/", current_folder) == 0); child = (new_folder && strlen(new_folder) != 0); /* Evolution back-end will support /telecom/pb folder only */ switch (flags) { case 0x02: /* Go back to root */ if (!child) { fullname = g_strdup("/"); goto done; } /* Go down 1 level */ fullname = g_build_filename(current_folder, new_folder, NULL); if (strcmp(PB_TELECOM_FOLDER, fullname) != 0 && strcmp(PB_CONTACTS_FOLDER, fullname) != 0) { g_free(fullname); fullname = NULL; ret = -ENOENT; } break; case 0x03: /* Go up 1 level */ if (root) { /* Already root */ ret = -EBADR; goto done; } /* * Removing one level of the current folder. Current folder * contains AT LEAST one level since it is not at root folder. * Use glib utility functions to handle invalid chars in the * folder path properly. */ tmp1 = g_path_get_basename(current_folder); tmp2 = g_strrstr(current_folder, tmp1); len = tmp2 - (current_folder + 1); g_free(tmp1); if (len == 0) base = g_strdup("/"); else base = g_strndup(current_folder, len); /* Return one level only */ if (!child) { fullname = base; goto done; } fullname = g_build_filename(base, new_folder, NULL); if (strcmp(fullname, PB_TELECOM_FOLDER) != 0 && strcmp(fullname, PB_CONTACTS_FOLDER) != 0) { g_free(fullname); fullname = NULL; ret = -ENOENT; } g_free(base); break; default: ret = -EBADR; break; } done: if (err) *err = ret; return fullname; } void phonebook_req_finalize(void *request) { struct query_context *data = request; if (data->queued_calls == 0) free_query_context(data); else data->canceled = TRUE; } void *phonebook_pull(const char *name, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct query_context *data; if (g_strcmp0(PB_CONTACTS, name) != 0) { if (err) *err = -ENOENT; return NULL; } data = g_new0(struct query_context, 1); data->contacts_cb = cb; data->params = params; data->user_data = user_data; data->buf = g_string_new(""); data->query = e_book_query_any_field_contains(""); data->ebooks = open_ebooks(); if (err) *err = data->ebooks == NULL ? -EIO : 0; return data; } int phonebook_pull_read(void *request) { struct query_context *data = request; GSList *l; if (!data) return -ENOENT; for (l = data->ebooks; l != NULL; l = g_slist_next(l)) { EBook *ebook = l->data; if (e_book_is_opened(ebook) == FALSE) continue; if (e_book_get_contacts_async(ebook, data->query, ebookpull_cb, data) == TRUE) data->queued_calls++; } if (data->queued_calls == 0) return -ENOENT; return 0; } void *phonebook_get_entry(const char *folder, const char *id, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct query_context *data; GSList *l; data = g_new0(struct query_context, 1); data->contacts_cb = cb; data->params = params; data->user_data = user_data; data->id = g_strdup(id); data->ebooks = open_ebooks(); for (l = data->ebooks; l != NULL; l = g_slist_next(l)) { EBook *ebook = l->data; if (e_book_is_opened(ebook) == FALSE) continue; if (e_book_get_contact_async(ebook, data->id, ebook_entry_cb, data) == TRUE) data->queued_calls++; } if (err) *err = (data->queued_calls == 0 ? -ENOENT : 0); return data; } void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, phonebook_cache_ready_cb ready_cb, void *user_data, int *err) { struct query_context *data; EBookQuery *query; GSList *l; EContact *me; EVCard *evcard; GError *gerr = NULL; EBook *eb; EVCardAttribute *attrib; char *uid, *tel, *cname; if (g_strcmp0(PB_CONTACTS_FOLDER, name) != 0) { if (err) *err = -ENOENT; return NULL; } DBG(""); query = e_book_query_any_field_contains(""); data = g_new0(struct query_context, 1); data->entry_cb = entry_cb; data->ready_cb = ready_cb; data->user_data = user_data; data->query = query; data->ebooks = open_ebooks(); /* Add 0.vcf */ if (e_book_get_self(&me, &eb, &gerr) == FALSE) { g_error_free(gerr); goto next; } evcard = E_VCARD(me); cname = evcard_name_attribute_to_string(evcard); if (!cname) cname = g_strdup(""); attrib = e_vcard_get_attribute(evcard, EVC_UID); uid = e_vcard_attribute_get_value(attrib); if (!uid) uid = g_strdup(""); attrib = e_vcard_get_attribute(evcard, EVC_TEL); if (attrib) tel = e_vcard_attribute_get_value(attrib); else tel = g_strdup(""); data->entry_cb(uid, 0, cname, NULL, tel, data->user_data); data->count++; g_free(cname); g_free(uid); g_free(tel); g_object_unref(eb); next: for (l = data->ebooks; l != NULL; l = g_slist_next(l)) { EBook *ebook = l->data; if (e_book_is_opened(ebook) == FALSE) continue; if (e_book_get_contacts_async(ebook, query, cache_cb, data) == TRUE) data->queued_calls++; } if (err) *err = (data->queued_calls == 0 ? -ENOENT : 0); return data; }