phonebook-dummy.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. *
  4. * OBEX Server
  5. *
  6. * Copyright (C) 2009-2010 Intel Corporation
  7. * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
  8. *
  9. *
  10. */
  11. #ifdef HAVE_CONFIG_H
  12. #include <config.h>
  13. #endif
  14. #define _GNU_SOURCE
  15. #include <dirent.h>
  16. #include <errno.h>
  17. #include <stdio.h>
  18. #include <stdint.h>
  19. #include <string.h>
  20. #include <glib.h>
  21. #include <stdlib.h>
  22. #include <sys/types.h>
  23. #include <sys/stat.h>
  24. #include <fcntl.h>
  25. #include <unistd.h>
  26. #include <libical/ical.h>
  27. #include <libical/vobject.h>
  28. #include <libical/vcc.h>
  29. #include "obexd/src/log.h"
  30. #include "phonebook.h"
  31. typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data);
  32. struct dummy_data {
  33. phonebook_cb cb;
  34. void *user_data;
  35. const struct apparam_field *apparams;
  36. char *folder;
  37. int fd;
  38. guint id;
  39. };
  40. struct cache_query {
  41. phonebook_entry_cb entry_cb;
  42. phonebook_cache_ready_cb ready_cb;
  43. void *user_data;
  44. DIR *dp;
  45. };
  46. static char *root_folder = NULL;
  47. static void dummy_free(void *user_data)
  48. {
  49. struct dummy_data *dummy = user_data;
  50. if (dummy->fd >= 0)
  51. close(dummy->fd);
  52. g_free(dummy->folder);
  53. g_free(dummy);
  54. }
  55. static void query_free(void *user_data)
  56. {
  57. struct cache_query *query = user_data;
  58. if (query->dp)
  59. closedir(query->dp);
  60. g_free(query);
  61. }
  62. int phonebook_init(void)
  63. {
  64. if (root_folder)
  65. return 0;
  66. /* FIXME: It should NOT be hard-coded */
  67. root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL);
  68. return 0;
  69. }
  70. void phonebook_exit(void)
  71. {
  72. g_free(root_folder);
  73. root_folder = NULL;
  74. }
  75. static int handle_cmp(gconstpointer a, gconstpointer b)
  76. {
  77. const char *f1 = a;
  78. const char *f2 = b;
  79. unsigned int i1, i2;
  80. if (sscanf(f1, "%u.vcf", &i1) != 1)
  81. return -1;
  82. if (sscanf(f2, "%u.vcf", &i2) != 1)
  83. return -1;
  84. return (i1 - i2);
  85. }
  86. static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset,
  87. uint16_t maxlistcount, void *user_data, uint16_t *count)
  88. {
  89. struct dirent *ep;
  90. GSList *sorted = NULL, *l;
  91. VObject *v;
  92. FILE *fp;
  93. int err, fd, folderfd;
  94. uint16_t n = 0;
  95. folderfd = dirfd(dp);
  96. if (folderfd < 0) {
  97. err = errno;
  98. error("dirfd(): %s(%d)", strerror(err), err);
  99. return -err;
  100. }
  101. /*
  102. * Sorting vcards by file name. versionsort is a GNU extension.
  103. * The simple sorting function implemented on handle_cmp address
  104. * vcards handle only(handle is always a number). This sort function
  105. * doesn't address filename started by "0".
  106. */
  107. while ((ep = readdir(dp))) {
  108. char *filename;
  109. if (ep->d_name[0] == '.')
  110. continue;
  111. filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
  112. if (filename == NULL) {
  113. error("g_filename_to_utf8: invalid filename");
  114. continue;
  115. }
  116. if (!g_str_has_suffix(filename, ".vcf")) {
  117. g_free(filename);
  118. continue;
  119. }
  120. sorted = g_slist_insert_sorted(sorted, filename, handle_cmp);
  121. }
  122. /*
  123. * Filtering only the requested vCards attributes. Offset
  124. * shall be based on the first entry of the phonebook.
  125. */
  126. for (l = g_slist_nth(sorted, offset);
  127. l && n < maxlistcount; l = l->next) {
  128. const char *filename = l->data;
  129. fd = openat(folderfd, filename, O_RDONLY);
  130. if (fd < 0) {
  131. err = errno;
  132. error("openat(%s): %s(%d)", filename, strerror(err), err);
  133. continue;
  134. }
  135. fp = fdopen(fd, "r");
  136. v = Parse_MIME_FromFile(fp);
  137. if (v != NULL) {
  138. func(filename, v, user_data);
  139. deleteVObject(v);
  140. n++;
  141. }
  142. close(fd);
  143. }
  144. g_slist_free_full(sorted, g_free);
  145. if (count)
  146. *count = n;
  147. return 0;
  148. }
  149. static void entry_concat(const char *filename, VObject *v, void *user_data)
  150. {
  151. GString *buffer = user_data;
  152. char tmp[1024];
  153. int len;
  154. /*
  155. * VObject API uses len for IN and OUT
  156. * Written bytes is also returned in the len variable
  157. */
  158. len = sizeof(tmp);
  159. memset(tmp, 0, len);
  160. writeMemVObject(tmp, &len, v);
  161. /* FIXME: only the requested fields must be added */
  162. g_string_append_len(buffer, tmp, len);
  163. }
  164. static gboolean read_dir(void *user_data)
  165. {
  166. struct dummy_data *dummy = user_data;
  167. GString *buffer;
  168. DIR *dp;
  169. uint16_t count = 0, max, offset;
  170. buffer = g_string_new("");
  171. dp = opendir(dummy->folder);
  172. if (dp == NULL) {
  173. int err = errno;
  174. DBG("opendir(): %s(%d)", strerror(err), err);
  175. goto done;
  176. }
  177. /*
  178. * For PullPhoneBook function, the decision of returning the size
  179. * or contacts is made in the PBAP core. When MaxListCount is ZERO,
  180. * PCE wants to know the size of a given folder, PSE shall ignore all
  181. * other applicattion parameters that may be present in the request.
  182. */
  183. if (dummy->apparams->maxlistcount == 0) {
  184. max = 0xffff;
  185. offset = 0;
  186. } else {
  187. max = dummy->apparams->maxlistcount;
  188. offset = dummy->apparams->liststartoffset;
  189. }
  190. foreach_vcard(dp, entry_concat, offset, max, buffer, &count);
  191. closedir(dp);
  192. done:
  193. /* FIXME: Missing vCards fields filtering */
  194. dummy->cb(buffer->str, buffer->len, count, 0, TRUE, dummy->user_data);
  195. g_string_free(buffer, TRUE);
  196. return FALSE;
  197. }
  198. static void entry_notify(const char *filename, VObject *v, void *user_data)
  199. {
  200. struct cache_query *query = user_data;
  201. VObject *property, *subproperty;
  202. GString *name;
  203. const char *tel;
  204. long unsigned int handle;
  205. property = isAPropertyOf(v, VCNameProp);
  206. if (!property)
  207. return;
  208. if (sscanf(filename, "%lu.vcf", &handle) != 1)
  209. return;
  210. if (handle > UINT32_MAX)
  211. return;
  212. /* LastName; FirstName; MiddleName; Prefix; Suffix */
  213. name = g_string_new("");
  214. subproperty = isAPropertyOf(property, VCFamilyNameProp);
  215. if (subproperty) {
  216. g_string_append(name,
  217. fakeCString(vObjectUStringZValue(subproperty)));
  218. }
  219. subproperty = isAPropertyOf(property, VCGivenNameProp);
  220. if (subproperty)
  221. g_string_append_printf(name, ";%s",
  222. fakeCString(vObjectUStringZValue(subproperty)));
  223. subproperty = isAPropertyOf(property, VCAdditionalNamesProp);
  224. if (subproperty)
  225. g_string_append_printf(name, ";%s",
  226. fakeCString(vObjectUStringZValue(subproperty)));
  227. subproperty = isAPropertyOf(property, VCNamePrefixesProp);
  228. if (subproperty)
  229. g_string_append_printf(name, ";%s",
  230. fakeCString(vObjectUStringZValue(subproperty)));
  231. subproperty = isAPropertyOf(property, VCNameSuffixesProp);
  232. if (subproperty)
  233. g_string_append_printf(name, ";%s",
  234. fakeCString(vObjectUStringZValue(subproperty)));
  235. property = isAPropertyOf(v, VCTelephoneProp);
  236. tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL;
  237. query->entry_cb(filename, handle, name->str, NULL, tel,
  238. query->user_data);
  239. g_string_free(name, TRUE);
  240. }
  241. static gboolean create_cache(void *user_data)
  242. {
  243. struct cache_query *query = user_data;
  244. /*
  245. * MaxListCount and ListStartOffset shall not be used
  246. * when creating the cache. All entries shall be fetched.
  247. * PBAP core is responsible for consider these application
  248. * parameters before reply the entries.
  249. */
  250. foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL);
  251. query->ready_cb(query->user_data);
  252. return FALSE;
  253. }
  254. static gboolean read_entry(void *user_data)
  255. {
  256. struct dummy_data *dummy = user_data;
  257. char buffer[1024];
  258. ssize_t count;
  259. memset(buffer, 0, sizeof(buffer));
  260. count = read(dummy->fd, buffer, sizeof(buffer));
  261. if (count < 0) {
  262. int err = errno;
  263. error("read(): %s(%d)", strerror(err), err);
  264. count = 0;
  265. }
  266. /* FIXME: Missing vCards fields filtering */
  267. dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data);
  268. return FALSE;
  269. }
  270. static gboolean is_dir(const char *dir)
  271. {
  272. struct stat st;
  273. if (stat(dir, &st) < 0) {
  274. int err = errno;
  275. error("stat(%s): %s (%d)", dir, strerror(err), err);
  276. return FALSE;
  277. }
  278. return S_ISDIR(st.st_mode);
  279. }
  280. char *phonebook_set_folder(const char *current_folder,
  281. const char *new_folder, uint8_t flags, int *err)
  282. {
  283. gboolean root, child;
  284. char *tmp1, *tmp2, *base, *absolute, *relative = NULL;
  285. int len, ret = 0;
  286. root = (g_strcmp0("/", current_folder) == 0);
  287. child = (new_folder && strlen(new_folder) != 0);
  288. switch (flags) {
  289. case 0x02:
  290. /* Go back to root */
  291. if (!child) {
  292. relative = g_strdup("/");
  293. goto done;
  294. }
  295. relative = g_build_filename(current_folder, new_folder, NULL);
  296. break;
  297. case 0x03:
  298. /* Go up 1 level */
  299. if (root) {
  300. /* Already root */
  301. ret = -EBADR;
  302. goto done;
  303. }
  304. /*
  305. * Removing one level of the current folder. Current folder
  306. * contains AT LEAST one level since it is not at root folder.
  307. * Use glib utility functions to handle invalid chars in the
  308. * folder path properly.
  309. */
  310. tmp1 = g_path_get_basename(current_folder);
  311. tmp2 = g_strrstr(current_folder, tmp1);
  312. len = tmp2 - (current_folder + 1);
  313. g_free(tmp1);
  314. if (len == 0)
  315. base = g_strdup("/");
  316. else
  317. base = g_strndup(current_folder, len);
  318. /* Return: one level only */
  319. if (!child) {
  320. relative = base;
  321. goto done;
  322. }
  323. relative = g_build_filename(base, new_folder, NULL);
  324. g_free(base);
  325. break;
  326. default:
  327. ret = -EBADR;
  328. break;
  329. }
  330. done:
  331. if (!relative) {
  332. if (err)
  333. *err = ret;
  334. return NULL;
  335. }
  336. absolute = g_build_filename(root_folder, relative, NULL);
  337. if (!is_dir(absolute)) {
  338. g_free(relative);
  339. relative = NULL;
  340. ret = -ENOENT;
  341. }
  342. g_free(absolute);
  343. if (err)
  344. *err = ret;
  345. return relative;
  346. }
  347. void phonebook_req_finalize(void *request)
  348. {
  349. struct dummy_data *dummy = request;
  350. /* dummy_data will be cleaned when request will be finished via
  351. * g_source_remove */
  352. if (dummy && dummy->id)
  353. g_source_remove(dummy->id);
  354. }
  355. void *phonebook_pull(const char *name, const struct apparam_field *params,
  356. phonebook_cb cb, void *user_data, int *err)
  357. {
  358. struct dummy_data *dummy;
  359. char *filename, *folder;
  360. /*
  361. * Main phonebook objects will be created dinamically based on the
  362. * folder content. All vcards inside the given folder will be appended
  363. * in the "virtual" main phonebook object.
  364. */
  365. filename = g_build_filename(root_folder, name, NULL);
  366. if (!g_str_has_suffix(filename, ".vcf")) {
  367. g_free(filename);
  368. if (err)
  369. *err = -EBADR;
  370. return NULL;
  371. }
  372. folder = g_strndup(filename, strlen(filename) - 4);
  373. g_free(filename);
  374. if (!is_dir(folder)) {
  375. g_free(folder);
  376. if (err)
  377. *err = -ENOENT;
  378. return NULL;
  379. }
  380. dummy = g_new0(struct dummy_data, 1);
  381. dummy->cb = cb;
  382. dummy->user_data = user_data;
  383. dummy->apparams = params;
  384. dummy->folder = folder;
  385. dummy->fd = -1;
  386. if (err)
  387. *err = 0;
  388. return dummy;
  389. }
  390. int phonebook_pull_read(void *request)
  391. {
  392. struct dummy_data *dummy = request;
  393. if (!dummy)
  394. return -ENOENT;
  395. dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_dir, dummy,
  396. dummy_free);
  397. return 0;
  398. }
  399. void *phonebook_get_entry(const char *folder, const char *id,
  400. const struct apparam_field *params, phonebook_cb cb,
  401. void *user_data, int *err)
  402. {
  403. struct dummy_data *dummy;
  404. char *filename;
  405. int fd;
  406. filename = g_build_filename(root_folder, folder, id, NULL);
  407. fd = open(filename, O_RDONLY);
  408. g_free(filename);
  409. if (fd < 0) {
  410. DBG("open(): %s(%d)", strerror(errno), errno);
  411. if (err)
  412. *err = -ENOENT;
  413. return NULL;
  414. }
  415. dummy = g_new0(struct dummy_data, 1);
  416. dummy->cb = cb;
  417. dummy->user_data = user_data;
  418. dummy->apparams = params;
  419. dummy->fd = fd;
  420. dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy,
  421. dummy_free);
  422. if (err)
  423. *err = 0;
  424. return dummy;
  425. }
  426. void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
  427. phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
  428. {
  429. struct cache_query *query;
  430. char *foldername;
  431. DIR *dp;
  432. struct dummy_data *dummy;
  433. foldername = g_build_filename(root_folder, name, NULL);
  434. dp = opendir(foldername);
  435. g_free(foldername);
  436. if (dp == NULL) {
  437. DBG("opendir(): %s(%d)", strerror(errno), errno);
  438. if (err)
  439. *err = -ENOENT;
  440. return NULL;
  441. }
  442. query = g_new0(struct cache_query, 1);
  443. query->entry_cb = entry_cb;
  444. query->ready_cb = ready_cb;
  445. query->user_data = user_data;
  446. query->dp = dp;
  447. dummy = g_new0(struct dummy_data, 1);
  448. dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache,
  449. query, query_free);
  450. if (err)
  451. *err = 0;
  452. return dummy;
  453. }