Upgrade bluez5_37 :Merge the code from private
[platform/upstream/bluez.git] / obexd / plugins / phonebook-dummy.c
1 /*
2  *
3  *  OBEX Server
4  *
5  *  Copyright (C) 2009-2010  Intel Corporation
6  *  Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
7  *
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22  *
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <dirent.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdint.h>
33 #include <string.h>
34 #include <glib.h>
35 #include <stdlib.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <libical/ical.h>
41 #include <libical/vobject.h>
42 #include <libical/vcc.h>
43
44 #include "obexd/src/log.h"
45 #include "phonebook.h"
46
47 typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data);
48
49 struct dummy_data {
50         phonebook_cb cb;
51         void *user_data;
52         const struct apparam_field *apparams;
53         char *folder;
54         int fd;
55         guint id;
56 };
57
58 struct cache_query {
59         phonebook_entry_cb entry_cb;
60         phonebook_cache_ready_cb ready_cb;
61         void *user_data;
62         DIR *dp;
63 };
64
65 static char *root_folder = NULL;
66
67 static void dummy_free(void *user_data)
68 {
69         struct dummy_data *dummy = user_data;
70
71         if (dummy->fd >= 0)
72                 close(dummy->fd);
73
74         g_free(dummy->folder);
75         g_free(dummy);
76 }
77
78 static void query_free(void *user_data)
79 {
80         struct cache_query *query = user_data;
81
82         if (query->dp)
83                 closedir(query->dp);
84
85         g_free(query);
86 }
87
88 int phonebook_init(void)
89 {
90         if (root_folder)
91                 return 0;
92
93         /* FIXME: It should NOT be hard-coded */
94         root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL);
95
96         return 0;
97 }
98
99 void phonebook_exit(void)
100 {
101         g_free(root_folder);
102         root_folder = NULL;
103 }
104
105 static int handle_cmp(gconstpointer a, gconstpointer b)
106 {
107         const char *f1 = a;
108         const char *f2 = b;
109         unsigned int i1, i2;
110
111         if (sscanf(f1, "%u.vcf", &i1) != 1)
112                 return -1;
113
114         if (sscanf(f2, "%u.vcf", &i2) != 1)
115                 return -1;
116
117         return (i1 - i2);
118 }
119
120 static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset,
121                         uint16_t maxlistcount, void *user_data, uint16_t *count)
122 {
123         struct dirent *ep;
124         GSList *sorted = NULL, *l;
125         VObject *v;
126         FILE *fp;
127         int err, fd, folderfd;
128         uint16_t n = 0;
129
130         folderfd = dirfd(dp);
131         if (folderfd < 0) {
132                 err = errno;
133                 error("dirfd(): %s(%d)", strerror(err), err);
134                 return -err;
135         }
136
137         /*
138          * Sorting vcards by file name. versionsort is a GNU extension.
139          * The simple sorting function implemented on handle_cmp address
140          * vcards handle only(handle is always a number). This sort function
141          * doesn't address filename started by "0".
142          */
143         while ((ep = readdir(dp))) {
144                 char *filename;
145
146                 if (ep->d_name[0] == '.')
147                         continue;
148
149                 filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
150                 if (filename == NULL) {
151                         error("g_filename_to_utf8: invalid filename");
152                         continue;
153                 }
154
155                 if (!g_str_has_suffix(filename, ".vcf")) {
156                         g_free(filename);
157                         continue;
158                 }
159
160                 sorted = g_slist_insert_sorted(sorted, filename, handle_cmp);
161         }
162
163         /*
164          * Filtering only the requested vCards attributes. Offset
165          * shall be based on the first entry of the phonebook.
166          */
167         for (l = g_slist_nth(sorted, offset);
168                         l && n < maxlistcount; l = l->next) {
169                 const char *filename = l->data;
170
171                 fd = openat(folderfd, filename, O_RDONLY);
172                 if (fd < 0) {
173                         err = errno;
174                         error("openat(%s): %s(%d)", filename, strerror(err), err);
175                         continue;
176                 }
177
178                 fp = fdopen(fd, "r");
179                 v = Parse_MIME_FromFile(fp);
180                 if (v != NULL) {
181                         func(filename, v, user_data);
182                         deleteVObject(v);
183                         n++;
184                 }
185
186                 close(fd);
187         }
188
189         g_slist_free_full(sorted, g_free);
190
191         if (count)
192                 *count = n;
193
194         return 0;
195 }
196
197 static void entry_concat(const char *filename, VObject *v, void *user_data)
198 {
199         GString *buffer = user_data;
200         char tmp[1024];
201         int len;
202
203         /*
204          * VObject API uses len for IN and OUT
205          * Written bytes is also returned in the len variable
206          */
207         len = sizeof(tmp);
208         memset(tmp, 0, len);
209
210         writeMemVObject(tmp, &len, v);
211
212         /* FIXME: only the requested fields must be added */
213         g_string_append_len(buffer, tmp, len);
214 }
215
216 static gboolean read_dir(void *user_data)
217 {
218         struct dummy_data *dummy = user_data;
219         GString *buffer;
220         DIR *dp;
221         uint16_t count = 0, max, offset;
222
223         buffer = g_string_new("");
224
225         dp = opendir(dummy->folder);
226         if (dp == NULL) {
227                 int err = errno;
228                 DBG("opendir(): %s(%d)", strerror(err), err);
229                 goto done;
230         }
231
232         /*
233          * For PullPhoneBook function, the decision of returning the size
234          * or contacts is made in the PBAP core. When MaxListCount is ZERO,
235          * PCE wants to know the size of a given folder, PSE shall ignore all
236          * other applicattion parameters that may be present in the request.
237          */
238         if (dummy->apparams->maxlistcount == 0) {
239                 max = 0xffff;
240                 offset = 0;
241         } else {
242                 max = dummy->apparams->maxlistcount;
243                 offset = dummy->apparams->liststartoffset;
244         }
245
246         foreach_vcard(dp, entry_concat, offset, max, buffer, &count);
247
248         closedir(dp);
249 done:
250         /* FIXME: Missing vCards fields filtering */
251         dummy->cb(buffer->str, buffer->len, count, 0, TRUE, dummy->user_data);
252
253         g_string_free(buffer, TRUE);
254
255         return FALSE;
256 }
257
258 static void entry_notify(const char *filename, VObject *v, void *user_data)
259 {
260         struct cache_query *query = user_data;
261         VObject *property, *subproperty;
262         GString *name;
263         const char *tel;
264         long unsigned int handle;
265
266         property = isAPropertyOf(v, VCNameProp);
267         if (!property)
268                 return;
269
270         if (sscanf(filename, "%lu.vcf", &handle) != 1)
271                 return;
272
273         if (handle > UINT32_MAX)
274                 return;
275
276         /* LastName; FirstName; MiddleName; Prefix; Suffix */
277
278         name = g_string_new("");
279         subproperty = isAPropertyOf(property, VCFamilyNameProp);
280         if (subproperty) {
281                 g_string_append(name,
282                                 fakeCString(vObjectUStringZValue(subproperty)));
283         }
284
285         subproperty = isAPropertyOf(property, VCGivenNameProp);
286         if (subproperty)
287                 g_string_append_printf(name, ";%s",
288                                 fakeCString(vObjectUStringZValue(subproperty)));
289
290         subproperty = isAPropertyOf(property, VCAdditionalNamesProp);
291         if (subproperty)
292                 g_string_append_printf(name, ";%s",
293                                 fakeCString(vObjectUStringZValue(subproperty)));
294
295         subproperty = isAPropertyOf(property, VCNamePrefixesProp);
296         if (subproperty)
297                 g_string_append_printf(name, ";%s",
298                                 fakeCString(vObjectUStringZValue(subproperty)));
299
300
301         subproperty = isAPropertyOf(property, VCNameSuffixesProp);
302         if (subproperty)
303                 g_string_append_printf(name, ";%s",
304                                 fakeCString(vObjectUStringZValue(subproperty)));
305
306         property = isAPropertyOf(v, VCTelephoneProp);
307
308         tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL;
309
310         query->entry_cb(filename, handle, name->str, NULL, tel,
311                                                         query->user_data);
312         g_string_free(name, TRUE);
313 }
314
315 static gboolean create_cache(void *user_data)
316 {
317         struct cache_query *query = user_data;
318
319         /*
320          * MaxListCount and ListStartOffset shall not be used
321          * when creating the cache. All entries shall be fetched.
322          * PBAP core is responsible for consider these application
323          * parameters before reply the entries.
324          */
325         foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL);
326
327         query->ready_cb(query->user_data);
328
329         return FALSE;
330 }
331
332 static gboolean read_entry(void *user_data)
333 {
334         struct dummy_data *dummy = user_data;
335         char buffer[1024];
336         ssize_t count;
337
338         memset(buffer, 0, sizeof(buffer));
339         count = read(dummy->fd, buffer, sizeof(buffer));
340
341         if (count < 0) {
342                 int err = errno;
343                 error("read(): %s(%d)", strerror(err), err);
344                 count = 0;
345         }
346
347         /* FIXME: Missing vCards fields filtering */
348
349         dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data);
350
351         return FALSE;
352 }
353
354 static gboolean is_dir(const char *dir)
355 {
356         struct stat st;
357
358         if (stat(dir, &st) < 0) {
359                 int err = errno;
360                 error("stat(%s): %s (%d)", dir, strerror(err), err);
361                 return FALSE;
362         }
363
364         return S_ISDIR(st.st_mode);
365 }
366
367 char *phonebook_set_folder(const char *current_folder,
368                 const char *new_folder, uint8_t flags, int *err)
369 {
370         gboolean root, child;
371         char *tmp1, *tmp2, *base, *absolute, *relative = NULL;
372         int len, ret = 0;
373
374         root = (g_strcmp0("/", current_folder) == 0);
375         child = (new_folder && strlen(new_folder) != 0);
376
377         switch (flags) {
378         case 0x02:
379                 /* Go back to root */
380                 if (!child) {
381                         relative = g_strdup("/");
382                         goto done;
383                 }
384
385                 relative = g_build_filename(current_folder, new_folder, NULL);
386                 break;
387         case 0x03:
388                 /* Go up 1 level */
389                 if (root) {
390                         /* Already root */
391                         ret = -EBADR;
392                         goto done;
393                 }
394
395                 /*
396                  * Removing one level of the current folder. Current folder
397                  * contains AT LEAST one level since it is not at root folder.
398                  * Use glib utility functions to handle invalid chars in the
399                  * folder path properly.
400                  */
401                 tmp1 = g_path_get_basename(current_folder);
402                 tmp2 = g_strrstr(current_folder, tmp1);
403                 len = tmp2 - (current_folder + 1);
404
405                 g_free(tmp1);
406
407                 if (len == 0)
408                         base = g_strdup("/");
409                 else
410                         base = g_strndup(current_folder, len);
411
412                 /* Return: one level only */
413                 if (!child) {
414                         relative = base;
415                         goto done;
416                 }
417
418                 relative = g_build_filename(base, new_folder, NULL);
419                 g_free(base);
420
421                 break;
422         default:
423                 ret = -EBADR;
424                 break;
425         }
426
427 done:
428         if (!relative) {
429                 if (err)
430                         *err = ret;
431
432                 return NULL;
433         }
434
435         absolute = g_build_filename(root_folder, relative, NULL);
436         if (!is_dir(absolute)) {
437                 g_free(relative);
438                 relative = NULL;
439                 ret = -ENOENT;
440         }
441
442         g_free(absolute);
443
444         if (err)
445                 *err = ret;
446
447         return relative;
448 }
449
450 void phonebook_req_finalize(void *request)
451 {
452         struct dummy_data *dummy = request;
453
454         /* dummy_data will be cleaned when request will be finished via
455          * g_source_remove */
456         if (dummy && dummy->id)
457                 g_source_remove(dummy->id);
458 }
459
460 void *phonebook_pull(const char *name, const struct apparam_field *params,
461                                 phonebook_cb cb, void *user_data, int *err)
462 {
463         struct dummy_data *dummy;
464         char *filename, *folder;
465
466         /*
467          * Main phonebook objects will be created dinamically based on the
468          * folder content. All vcards inside the given folder will be appended
469          * in the "virtual" main phonebook object.
470          */
471
472         filename = g_build_filename(root_folder, name, NULL);
473
474         if (!g_str_has_suffix(filename, ".vcf")) {
475                 g_free(filename);
476                 if (err)
477                         *err = -EBADR;
478                 return NULL;
479         }
480
481         folder = g_strndup(filename, strlen(filename) - 4);
482         g_free(filename);
483         if (!is_dir(folder)) {
484                 g_free(folder);
485                 if (err)
486                         *err = -ENOENT;
487                 return NULL;
488         }
489
490         dummy = g_new0(struct dummy_data, 1);
491         dummy->cb = cb;
492         dummy->user_data = user_data;
493         dummy->apparams = params;
494         dummy->folder = folder;
495         dummy->fd = -1;
496
497         if (err)
498                 *err = 0;
499
500         return dummy;
501 }
502
503 int phonebook_pull_read(void *request)
504 {
505         struct dummy_data *dummy = request;
506
507         if (!dummy)
508                 return -ENOENT;
509
510         dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_dir, dummy,
511                                                                 dummy_free);
512
513         return 0;
514 }
515
516 void *phonebook_get_entry(const char *folder, const char *id,
517                         const struct apparam_field *params, phonebook_cb cb,
518                         void *user_data, int *err)
519 {
520         struct dummy_data *dummy;
521         char *filename;
522         int fd;
523         guint ret;
524
525         filename = g_build_filename(root_folder, folder, id, NULL);
526
527         fd = open(filename, O_RDONLY);
528
529 #ifdef __TIZEN_PATCH__
530         g_free(filename);
531 #endif
532
533         if (fd < 0) {
534                 DBG("open(): %s(%d)", strerror(errno), errno);
535                 if (err)
536                         *err = -ENOENT;
537                 return NULL;
538         }
539
540         dummy = g_new0(struct dummy_data, 1);
541         dummy->cb = cb;
542         dummy->user_data = user_data;
543         dummy->apparams = params;
544         dummy->fd = fd;
545
546         ret = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy,
547                                                                 dummy_free);
548
549         if (err)
550                 *err = 0;
551
552         return GINT_TO_POINTER(ret);
553 }
554
555 void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
556                 phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
557 {
558         struct cache_query *query;
559         char *foldername;
560         DIR *dp;
561         guint ret;
562
563         foldername = g_build_filename(root_folder, name, NULL);
564         dp = opendir(foldername);
565         g_free(foldername);
566
567         if (dp == NULL) {
568                 DBG("opendir(): %s(%d)", strerror(errno), errno);
569                 if (err)
570                         *err = -ENOENT;
571                 return NULL;
572         }
573
574         query = g_new0(struct cache_query, 1);
575         query->entry_cb = entry_cb;
576         query->ready_cb = ready_cb;
577         query->user_data = user_data;
578         query->dp = dp;
579
580         ret = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache, query,
581                                                                 query_free);
582
583         if (err)
584                 *err = 0;
585
586         return GINT_TO_POINTER(ret);
587 }