| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- *
- * OBEX Server
- *
- * Copyright (C) 2009-2010 Intel Corporation
- * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
- #ifdef HAVE_CONFIG_H
- #include <config.h>
- #endif
- #define _GNU_SOURCE
- #include <dirent.h>
- #include <errno.h>
- #include <stdio.h>
- #include <stdint.h>
- #include <string.h>
- #include <glib.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <libical/ical.h>
- #include <libical/vobject.h>
- #include <libical/vcc.h>
- #include "obexd/src/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;
- filename = g_build_filename(root_folder, folder, id, NULL);
- fd = open(filename, O_RDONLY);
- g_free(filename);
- 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;
- dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy,
- dummy_free);
- if (err)
- *err = 0;
- return dummy;
- }
- 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;
- struct dummy_data *dummy;
- 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;
- dummy = g_new0(struct dummy_data, 1);
- dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache,
- query, query_free);
- if (err)
- *err = 0;
- return dummy;
- }
|