/* * * 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 #include #include #include #include #include #include #include #include #include "log.h" #include "phonebook.h" typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data); struct dummy_data { phonebook_cb cb; void *user_data; const struct apparam_field *apparams; char *folder; int fd; guint id; }; struct cache_query { phonebook_entry_cb entry_cb; phonebook_cache_ready_cb ready_cb; void *user_data; DIR *dp; }; static char *root_folder = NULL; static void dummy_free(void *user_data) { struct dummy_data *dummy = user_data; if (dummy->fd >= 0) close(dummy->fd); g_free(dummy->folder); g_free(dummy); } static void query_free(void *user_data) { struct cache_query *query = user_data; if (query->dp) closedir(query->dp); g_free(query); } int phonebook_init(void) { if (root_folder) return 0; /* FIXME: It should NOT be hard-coded */ root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL); return 0; } void phonebook_exit(void) { g_free(root_folder); root_folder = NULL; } static int handle_cmp(gconstpointer a, gconstpointer b) { const char *f1 = a; const char *f2 = b; unsigned int i1, i2; if (sscanf(f1, "%u.vcf", &i1) != 1) return -1; if (sscanf(f2, "%u.vcf", &i2) != 1) return -1; return (i1 - i2); } static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset, uint16_t maxlistcount, void *user_data, uint16_t *count) { struct dirent *ep; GSList *sorted = NULL, *l; VObject *v; FILE *fp; int err, fd, folderfd; uint16_t n = 0; folderfd = dirfd(dp); if (folderfd < 0) { err = errno; error("dirfd(): %s(%d)", strerror(err), err); return -err; } /* * Sorting vcards by file name. versionsort is a GNU extension. * The simple sorting function implemented on handle_cmp address * vcards handle only(handle is always a number). This sort function * doesn't address filename started by "0". */ while ((ep = readdir(dp))) { char *filename; if (ep->d_name[0] == '.') continue; filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); if (filename == NULL) { error("g_filename_to_utf8: invalid filename"); continue; } if (!g_str_has_suffix(filename, ".vcf")) { g_free(filename); continue; } sorted = g_slist_insert_sorted(sorted, filename, handle_cmp); } /* * Filtering only the requested vCards attributes. Offset * shall be based on the first entry of the phonebook. */ for (l = g_slist_nth(sorted, offset); l && n < maxlistcount; l = l->next) { const char *filename = l->data; fd = openat(folderfd, filename, O_RDONLY); if (fd < 0) { err = errno; error("openat(%s): %s(%d)", filename, strerror(err), err); continue; } fp = fdopen(fd, "r"); v = Parse_MIME_FromFile(fp); if (v != NULL) { func(filename, v, user_data); deleteVObject(v); n++; } close(fd); } g_slist_free_full(sorted, g_free); if (count) *count = n; return 0; } static void entry_concat(const char *filename, VObject *v, void *user_data) { GString *buffer = user_data; char tmp[1024]; int len; /* * VObject API uses len for IN and OUT * Written bytes is also returned in the len variable */ len = sizeof(tmp); memset(tmp, 0, len); writeMemVObject(tmp, &len, v); /* FIXME: only the requested fields must be added */ g_string_append_len(buffer, tmp, len); } static gboolean read_dir(void *user_data) { struct dummy_data *dummy = user_data; GString *buffer; DIR *dp; uint16_t count = 0, max, offset; buffer = g_string_new(""); dp = opendir(dummy->folder); if (dp == NULL) { int err = errno; DBG("opendir(): %s(%d)", strerror(err), err); goto done; } /* * For PullPhoneBook function, the decision of returning the size * or contacts is made in the PBAP core. When MaxListCount is ZERO, * PCE wants to know the size of a given folder, PSE shall ignore all * other applicattion parameters that may be present in the request. */ if (dummy->apparams->maxlistcount == 0) { max = 0xffff; offset = 0; } else { max = dummy->apparams->maxlistcount; offset = dummy->apparams->liststartoffset; } foreach_vcard(dp, entry_concat, offset, max, buffer, &count); closedir(dp); done: /* FIXME: Missing vCards fields filtering */ dummy->cb(buffer->str, buffer->len, count, 0, TRUE, dummy->user_data); g_string_free(buffer, TRUE); return FALSE; } static void entry_notify(const char *filename, VObject *v, void *user_data) { struct cache_query *query = user_data; VObject *property, *subproperty; GString *name; const char *tel; long unsigned int handle; property = isAPropertyOf(v, VCNameProp); if (!property) return; if (sscanf(filename, "%lu.vcf", &handle) != 1) return; if (handle > UINT32_MAX) return; /* LastName; FirstName; MiddleName; Prefix; Suffix */ name = g_string_new(""); subproperty = isAPropertyOf(property, VCFamilyNameProp); if (subproperty) { g_string_append(name, fakeCString(vObjectUStringZValue(subproperty))); } subproperty = isAPropertyOf(property, VCGivenNameProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); subproperty = isAPropertyOf(property, VCAdditionalNamesProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); subproperty = isAPropertyOf(property, VCNamePrefixesProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); subproperty = isAPropertyOf(property, VCNameSuffixesProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); property = isAPropertyOf(v, VCTelephoneProp); tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL; query->entry_cb(filename, handle, name->str, NULL, tel, query->user_data); g_string_free(name, TRUE); } static gboolean create_cache(void *user_data) { struct cache_query *query = user_data; /* * MaxListCount and ListStartOffset shall not be used * when creating the cache. All entries shall be fetched. * PBAP core is responsible for consider these application * parameters before reply the entries. */ foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL); query->ready_cb(query->user_data); return FALSE; } static gboolean read_entry(void *user_data) { struct dummy_data *dummy = user_data; char buffer[1024]; ssize_t count; memset(buffer, 0, sizeof(buffer)); count = read(dummy->fd, buffer, sizeof(buffer)); if (count < 0) { int err = errno; error("read(): %s(%d)", strerror(err), err); count = 0; } /* FIXME: Missing vCards fields filtering */ dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data); return FALSE; } static gboolean is_dir(const char *dir) { struct stat st; if (stat(dir, &st) < 0) { int err = errno; error("stat(%s): %s (%d)", dir, strerror(err), err); return FALSE; } return S_ISDIR(st.st_mode); } char *phonebook_set_folder(const char *current_folder, const char *new_folder, uint8_t flags, int *err) { gboolean root, child; char *tmp1, *tmp2, *base, *absolute, *relative = NULL; int len, ret = 0; root = (g_strcmp0("/", current_folder) == 0); child = (new_folder && strlen(new_folder) != 0); switch (flags) { case 0x02: /* Go back to root */ if (!child) { relative = g_strdup("/"); goto done; } relative = g_build_filename(current_folder, new_folder, NULL); 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) { relative = base; goto done; } relative = g_build_filename(base, new_folder, NULL); g_free(base); break; default: ret = -EBADR; break; } done: if (!relative) { if (err) *err = ret; return NULL; } absolute = g_build_filename(root_folder, relative, NULL); if (!is_dir(absolute)) { g_free(relative); relative = NULL; ret = -ENOENT; } g_free(absolute); if (err) *err = ret; return relative; } void phonebook_req_finalize(void *request) { struct dummy_data *dummy = request; /* dummy_data will be cleaned when request will be finished via * g_source_remove */ if (dummy && dummy->id) g_source_remove(dummy->id); } void *phonebook_pull(const char *name, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct dummy_data *dummy; char *filename, *folder; /* * Main phonebook objects will be created dinamically based on the * folder content. All vcards inside the given folder will be appended * in the "virtual" main phonebook object. */ filename = g_build_filename(root_folder, name, NULL); if (!g_str_has_suffix(filename, ".vcf")) { g_free(filename); if (err) *err = -EBADR; return NULL; } folder = g_strndup(filename, strlen(filename) - 4); g_free(filename); if (!is_dir(folder)) { g_free(folder); if (err) *err = -ENOENT; return NULL; } dummy = g_new0(struct dummy_data, 1); dummy->cb = cb; dummy->user_data = user_data; dummy->apparams = params; dummy->folder = folder; dummy->fd = -1; if (err) *err = 0; return dummy; } int phonebook_pull_read(void *request) { struct dummy_data *dummy = request; if (!dummy) return -ENOENT; dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_dir, dummy, dummy_free); 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 dummy_data *dummy; char *filename; int fd; guint ret; filename = g_build_filename(root_folder, folder, id, NULL); fd = open(filename, O_RDONLY); if (fd < 0) { DBG("open(): %s(%d)", strerror(errno), errno); if (err) *err = -ENOENT; return NULL; } dummy = g_new0(struct dummy_data, 1); dummy->cb = cb; dummy->user_data = user_data; dummy->apparams = params; dummy->fd = fd; ret = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy, dummy_free); if (err) *err = 0; return GINT_TO_POINTER(ret); } void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, phonebook_cache_ready_cb ready_cb, void *user_data, int *err) { struct cache_query *query; char *foldername; DIR *dp; guint ret; foldername = g_build_filename(root_folder, name, NULL); dp = opendir(foldername); g_free(foldername); if (dp == NULL) { DBG("opendir(): %s(%d)", strerror(errno), errno); if (err) *err = -ENOENT; return NULL; } query = g_new0(struct cache_query, 1); query->entry_cb = entry_cb; query->ready_cb = ready_cb; query->user_data = user_data; query->dp = dp; ret = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache, query, query_free); if (err) *err = 0; return GINT_TO_POINTER(ret); }