blob: 035ec35786ff5e13fe4a068dbfd28db0e449c42e [file] [log] [blame]
/*
*
* OBEX Server
*
* Copyright (C) 2009-2010 Intel Corporation
* Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
* 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 <config.h>
#endif
#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 "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, 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);
}