diff options
Diffstat (limited to 'obexd/plugins/vcard.c')
-rw-r--r-- | obexd/plugins/vcard.c | 924 |
1 files changed, 924 insertions, 0 deletions
diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c new file mode 100644 index 00000000..b36e4bff --- /dev/null +++ b/obexd/plugins/vcard.c @@ -0,0 +1,924 @@ +/* + * OBEX Server + * + * Copyright (C) 2008-2010 Intel Corporation. All rights reserved. + * + * 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 <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <ctype.h> +#include <errno.h> + +#include <glib.h> +#include <gdbus/gdbus.h> + +#include "vcard.h" + +#define ADDR_FIELD_AMOUNT 7 +#define LEN_MAX 128 +#define TYPE_INTERNATIONAL 145 + +#define PHONEBOOK_FLAG_CACHED 0x1 + +#define FILTER_VERSION (1 << 0) +#define FILTER_FN (1 << 1) +#define FILTER_N (1 << 2) +#define FILTER_PHOTO (1 << 3) +#define FILTER_BDAY (1 << 4) +#define FILTER_ADR (1 << 5) +#define FILTER_LABEL (1 << 6) +#define FILTER_TEL (1 << 7) +#define FILTER_EMAIL (1 << 8) +#define FILTER_MAILER (1 << 9) +#define FILTER_TZ (1 << 10) +#define FILTER_GEO (1 << 11) +#define FILTER_TITLE (1 << 12) +#define FILTER_ROLE (1 << 13) +#define FILTER_LOGO (1 << 14) +#define FILTER_AGENT (1 << 15) +#define FILTER_ORG (1 << 16) +#define FILTER_NOTE (1 << 17) +#define FILTER_REV (1 << 18) +#define FILTER_SOUND (1 << 19) +#define FILTER_URL (1 << 20) +#define FILTER_UID (1 << 21) +#define FILTER_KEY (1 << 22) +#define FILTER_NICKNAME (1 << 23) +#define FILTER_CATEGORIES (1 << 24) +#define FILTER_PROID (1 << 25) +#define FILTER_CLASS (1 << 26) +#define FILTER_SORT_STRING (1 << 27) +#define FILTER_X_IRMC_CALL_DATETIME (1 << 28) + +#define FORMAT_VCARD21 0x00 +#define FORMAT_VCARD30 0x01 + +#define QP_LINE_LEN 75 +#define QP_CHAR_LEN 3 +#define QP_CR 0x0D +#define QP_LF 0x0A +#define QP_ESC 0x5C +#define QP_SOFT_LINE_BREAK "=" +#define QP_SELECT "\n!\"#$=@[\\]^`{|}~" +#define ASCII_LIMIT 0x7F + +/* according to RFC 2425, the output string may need folding */ +static void vcard_printf(GString *str, const char *fmt, ...) +{ + char buf[1024]; + va_list ap; + int len_temp, line_number, i; + unsigned int line_delimit = 75; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + line_number = strlen(buf) / line_delimit + 1; + + for (i = 0; i < line_number; i++) { + len_temp = MIN(line_delimit, strlen(buf) - line_delimit * i); + g_string_append_len(str, buf + line_delimit * i, len_temp); + if (i != line_number - 1) + g_string_append(str, "\r\n "); + } + + g_string_append(str, "\r\n"); +} + +/* According to RFC 2426, we need escape following characters: + * '\n', '\r', ';', ',', '\'. + */ +static void add_slash(char *dest, const char *src, int len_max, int len) +{ + int i, j; + + for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) { + /* filling dest buffer - last field need to be reserved + * for '\0'*/ + switch (src[i]) { + case '\n': + if (j + 2 >= len_max) + /* not enough space in the buffer to put char + * preceded with escaping sequence (and '\0' in + * the end) */ + goto done; + + dest[j++] = '\\'; + dest[j] = 'n'; + break; + case '\r': + if (j + 2 >= len_max) + goto done; + + dest[j++] = '\\'; + dest[j] = 'r'; + break; + case '\\': + case ';': + case ',': + if (j + 2 >= len_max) + goto done; + + dest[j++] = '\\'; + default: + dest[j] = src[i]; + break; + } + } + +done: + dest[j] = 0; +} + +static void escape_semicolon(char *dest, const char *src, int len_max, int len) +{ + int i, j; + + for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) { + if (src[i] == ';') { + if (j + 2 >= len_max) + break; + + dest[j++] = '\\'; + } + + dest[j] = src[i]; + } + + dest[j] = 0; +} + +static void set_escape(uint8_t format, char *dest, const char *src, + int len_max, int len) +{ + if (format == FORMAT_VCARD30) + add_slash(dest, src, len_max, len); + else if (format == FORMAT_VCARD21) + escape_semicolon(dest, src, len_max, len); +} + +static void get_escaped_fields(uint8_t format, char **fields, ...) +{ + va_list ap; + GString *line; + char *field; + char escaped[LEN_MAX]; + + va_start(ap, fields); + line = g_string_new(""); + + for (field = va_arg(ap, char *); field; ) { + set_escape(format, escaped, field, LEN_MAX, strlen(field)); + g_string_append(line, escaped); + + field = va_arg(ap, char *); + + if (field) + g_string_append(line, ";"); + } + + va_end(ap); + + *fields = g_string_free(line, FALSE); +} + +static gboolean set_qp_encoding(char c) +{ + unsigned char q = c; + + if (strchr(QP_SELECT, q) != NULL) + return TRUE; + + if (q < '!' || q > '~') + return TRUE; + + return FALSE; +} + +static void append_qp_break_line(GString *vcards, size_t *limit) +{ + /* Quoted Printable lines of text must be limited to less than 76 + * characters and terminated by Quoted Printable softline break + * sequence of "=" (if some more characters left) */ + g_string_append(vcards, QP_SOFT_LINE_BREAK); + g_string_append(vcards, "\r\n "); + *limit = QP_LINE_LEN - 1; +} + +static void append_qp_ascii(GString *vcards, size_t *limit, char c) +{ + if (*limit == 0) + append_qp_break_line(vcards, limit); + + g_string_append_c(vcards, c); + --*limit; +} + +static void append_qp_hex(GString *vcards, size_t *limit, char c) +{ + if (*limit < QP_CHAR_LEN) + append_qp_break_line(vcards, limit); + + g_string_append_printf(vcards, "=%2.2X", (unsigned char) c); + *limit -= QP_CHAR_LEN; +} + +static void append_qp_new_line(GString *vcards, size_t *limit) +{ + /* Multiple lines of text are separated with a Quoted Printable CRLF + * sequence of "=0D" followed by "=0A" followed by a Quoted Printable + * softline break sequence of "=" */ + append_qp_hex(vcards, limit, QP_CR); + append_qp_hex(vcards, limit, QP_LF); + append_qp_break_line(vcards, limit); +} + +static gboolean utf8_select(const char *field) +{ + const char *pos; + + if (g_utf8_validate(field, -1, NULL) == FALSE) + return FALSE; + + for (pos = field; *pos != '\0'; pos = g_utf8_next_char(pos)) { + /* Test for non-standard UTF-8 character (out of range + * standard ASCII set), composed of more than single byte + * and represented by 32-bit value greater than 0x7F */ + if (g_utf8_get_char(pos) > ASCII_LIMIT) + return TRUE; + } + + return FALSE; +} + +static void vcard_qp_print_encoded(GString *vcards, const char *desc, ...) +{ + const char *field, *charset = ""; + const char *encoding = ";ENCODING=QUOTED-PRINTABLE"; + size_t limit, param_len; + va_list ap; + + va_start(ap, desc); + + for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) { + if (utf8_select(field) == TRUE) { + charset = ";CHARSET=UTF-8"; + break; + } + } + + va_end(ap); + + vcard_printf(vcards, "%s%s%s:", desc, encoding, charset); + g_string_truncate(vcards, vcards->len - 2); + + param_len = strlen(desc) + strlen(encoding) + strlen(charset) + 1; + limit = QP_LINE_LEN - param_len; + + va_start(ap, desc); + + for (field = va_arg(ap, char *); field != NULL; ) { + size_t i, size = strlen(field); + + for (i = 0; i < size; ++i) { + if (set_qp_encoding(field[i])) { + if (field[i] == '\n') { + append_qp_new_line(vcards, &limit); + continue; + } + + append_qp_hex(vcards, &limit, field[i]); + } else { + /* According to vCard 2.1 spec. semicolons in + * property parameter value must be escaped */ + if (field[i] == ';') + append_qp_hex(vcards, &limit, QP_ESC); + + append_qp_ascii(vcards, &limit, field[i]); + } + } + + field = va_arg(ap, char *); + if (field) + append_qp_ascii(vcards, &limit, ';'); + } + + va_end(ap); + + g_string_append(vcards, "\r\n"); +} + +static gboolean select_qp_encoding(uint8_t format, ...) +{ + char *field; + va_list ap; + + if (format != FORMAT_VCARD21) + return FALSE; + + va_start(ap, format); + + for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) { + int i; + unsigned char c; + + if (strpbrk(field, QP_SELECT)) { + va_end(ap); + return TRUE; + } + + /* Quoted Printable encoding is selected if there is + * a character, which value is out of range standard + * ASCII set, since it may be a part of some + * non-standard character such as specified by UTF-8 */ + for (i = 0; (c = field[i]) != '\0'; ++i) { + if (c > ASCII_LIMIT) { + va_end(ap); + return TRUE; + } + } + } + + va_end(ap); + + return FALSE; +} + +static void vcard_printf_begin(GString *vcards, uint8_t format) +{ + vcard_printf(vcards, "BEGIN:VCARD"); + + if (format == FORMAT_VCARD30) + vcard_printf(vcards, "VERSION:3.0"); + else if (format == FORMAT_VCARD21) + vcard_printf(vcards, "VERSION:2.1"); +} + +/* check if there is at least one contact field with personal data present */ +static gboolean contact_fields_present(struct phonebook_contact * contact) +{ + if (contact->family && strlen(contact->family) > 0) + return TRUE; + + if (contact->given && strlen(contact->given) > 0) + return TRUE; + + if (contact->additional && strlen(contact->additional) > 0) + return TRUE; + + if (contact->prefix && strlen(contact->prefix) > 0) + return TRUE; + + if (contact->suffix && strlen(contact->suffix) > 0) + return TRUE; + + /* none of the personal data fields are present*/ + return FALSE; +} + +static void vcard_printf_name(GString *vcards, uint8_t format, + struct phonebook_contact *contact) +{ + char *fields; + + if (contact_fields_present(contact) == FALSE) { + /* If fields are empty, add only 'N:' as parameter. + * This is crucial for some devices (Nokia BH-903) which + * have problems with history listings and can't determine + * that a parameter is really empty if there are unnecessary + * characters after 'N:' (e.g. 'N:;;;;'). + * We need to add only'N:' param - without semicolons. + */ + vcard_printf(vcards, "N:"); + return; + } + + if (select_qp_encoding(format, contact->family, contact->given, + contact->additional, contact->prefix, + contact->suffix, NULL)) { + vcard_qp_print_encoded(vcards, "N", contact->family, + contact->given, contact->additional, + contact->prefix, contact->suffix, + NULL); + return; + } + + get_escaped_fields(format, &fields, contact->family, + contact->given, contact->additional, + contact->prefix, contact->suffix, + NULL); + + vcard_printf(vcards, "N:%s", fields); + + g_free(fields); +} + +static void vcard_printf_fullname(GString *vcards, uint8_t format, + const char *text) +{ + char field[LEN_MAX]; + + if (!text || strlen(text) == 0) { + vcard_printf(vcards, "FN:"); + return; + } + + if (select_qp_encoding(format, text, NULL)) { + vcard_qp_print_encoded(vcards, "FN", text, NULL); + return; + } + + set_escape(format, field, text, LEN_MAX, strlen(text)); + vcard_printf(vcards, "FN:%s", field); +} + +static void vcard_printf_number(GString *vcards, uint8_t format, + const char *number, int type, + enum phonebook_number_type category) +{ + const char *intl = "", *category_string = ""; + char buf[LEN_MAX], field[LEN_MAX]; + + /* TEL is a mandatory field, include even if empty */ + if (!number || !strlen(number) || !type) { + vcard_printf(vcards, "TEL:"); + return; + } + + switch (category) { + case TEL_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "HOME;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=HOME;TYPE=VOICE"; + break; + case TEL_TYPE_MOBILE: + if (format == FORMAT_VCARD21) + category_string = "CELL;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=CELL;TYPE=VOICE"; + break; + case TEL_TYPE_FAX: + if (format == FORMAT_VCARD21) + category_string = "FAX"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=FAX"; + break; + case TEL_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "WORK;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=WORK;TYPE=VOICE"; + break; + case TEL_TYPE_OTHER: + if (format == FORMAT_VCARD21) + category_string = "OTHER;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=OTHER;TYPE=VOICE"; + break; + } + + if ((type == TYPE_INTERNATIONAL) && (number[0] != '+')) + intl = "+"; + + snprintf(field, sizeof(field), "%s%s", intl, number); + + if (select_qp_encoding(format, number, NULL)) { + snprintf(buf, sizeof(buf), "TEL;%s", category_string); + vcard_qp_print_encoded(vcards, buf, field, NULL); + return; + } + + vcard_printf(vcards, "TEL;%s:%s", category_string, field); +} + +static void vcard_printf_tag(GString *vcards, uint8_t format, + const char *tag, const char *category, + const char *fld) +{ + int len; + char *separator = "", *type = ""; + char buf[LEN_MAX], field[LEN_MAX]; + + if (tag == NULL || strlen(tag) == 0) + return; + + if (fld == NULL || (len = strlen(fld)) == 0) { + vcard_printf(vcards, "%s:", tag); + return; + } + + if (category && strlen(category)) { + separator = ";"; + if (format == FORMAT_VCARD30) + type = "TYPE="; + } else { + category = ""; + } + + snprintf(buf, LEN_MAX, "%s%s%s%s", tag, separator, type, category); + + if (select_qp_encoding(format, fld, NULL)) { + vcard_qp_print_encoded(vcards, buf, fld, NULL); + return; + } + + set_escape(format, field, fld, LEN_MAX, len); + vcard_printf(vcards, "%s:%s", buf, field); +} + +static void vcard_printf_email(GString *vcards, uint8_t format, + const char *address, + enum phonebook_field_type category) +{ + const char *category_string = ""; + char buf[LEN_MAX], field[LEN_MAX]; + int len = 0; + + if (!address || !(len = strlen(address))) { + vcard_printf(vcards, "EMAIL:"); + return; + } + switch (category) { + case FIELD_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;HOME"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=HOME"; + break; + case FIELD_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;WORK"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=WORK"; + break; + default: + if (format == FORMAT_VCARD21) + category_string = "INTERNET"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=OTHER"; + } + + if (select_qp_encoding(format, address, NULL)) { + snprintf(buf, sizeof(buf), "EMAIL;%s", category_string); + vcard_qp_print_encoded(vcards, buf, address, NULL); + return; + } + + set_escape(format, field, address, LEN_MAX, len); + vcard_printf(vcards, "EMAIL;%s:%s", category_string, field); +} + +static void vcard_printf_url(GString *vcards, uint8_t format, + const char *url, + enum phonebook_field_type category) +{ + const char *category_string = ""; + char buf[LEN_MAX], field[LEN_MAX]; + + if (!url || strlen(url) == 0) { + vcard_printf(vcards, "URL:"); + return; + } + + switch (category) { + case FIELD_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;HOME"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=HOME"; + break; + case FIELD_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;WORK"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=WORK"; + break; + default: + if (format == FORMAT_VCARD21) + category_string = "INTERNET"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET"; + break; + } + + if (select_qp_encoding(format, url, NULL)) { + snprintf(buf, sizeof(buf), "URL;%s", category_string); + vcard_qp_print_encoded(vcards, buf, url, NULL); + return; + } + + set_escape(format, field, url, LEN_MAX, strlen(url)); + vcard_printf(vcards, "URL;%s:%s", category_string, field); +} + +static gboolean org_fields_present(struct phonebook_contact *contact) +{ + if (contact->company && strlen(contact->company)) + return TRUE; + + if (contact->department && strlen(contact->department)) + return TRUE; + + return FALSE; +} + +static void vcard_printf_org(GString *vcards, uint8_t format, + struct phonebook_contact *contact) +{ + char *fields; + + if (org_fields_present(contact) == FALSE) + return; + + if (select_qp_encoding(format, contact->company, + contact->department, NULL)) { + vcard_qp_print_encoded(vcards, "ORG", contact->company, + contact->department, NULL); + return; + } + + get_escaped_fields(format, &fields, contact->company, + contact->department, NULL); + + vcard_printf(vcards, "ORG:%s", fields); + + g_free(fields); +} + +static void vcard_printf_address(GString *vcards, uint8_t format, + struct phonebook_addr *address) +{ + char *fields, field_esc[LEN_MAX]; + const char *category_string = ""; + char buf[LEN_MAX], *address_fields[ADDR_FIELD_AMOUNT]; + int i; + size_t len; + GSList *l; + + if (!address) { + vcard_printf(vcards, "ADR:"); + return; + } + + switch (address->type) { + case FIELD_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "HOME"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=HOME"; + break; + case FIELD_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "WORK"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=WORK"; + break; + default: + if (format == FORMAT_VCARD21) + category_string = "OTHER"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=OTHER"; + break; + } + + for (i = 0, l = address->fields; l; l = l->next) + address_fields[i++] = l->data; + + if (select_qp_encoding(format, address_fields[0], address_fields[1], + address_fields[2], address_fields[3], + address_fields[4], address_fields[5], + address_fields[6], NULL)) { + snprintf(buf, sizeof(buf), "ADR;%s", category_string); + vcard_qp_print_encoded(vcards, buf, + address_fields[0], address_fields[1], + address_fields[2], address_fields[3], + address_fields[4], address_fields[5], + address_fields[6], NULL); + return; + } + + /* allocate enough memory to insert address fields separated by ';' + * and terminated by '\0' */ + len = ADDR_FIELD_AMOUNT * LEN_MAX; + fields = g_malloc0(len); + + for (l = address->fields; l; l = l->next) { + char *field = l->data; + + if (field) { + set_escape(format, field_esc, field, LEN_MAX, + strlen(field)); + g_strlcat(fields, field_esc, len); + } + + if (l->next) + /* not adding ';' after last addr field */ + g_strlcat(fields, ";", len); + } + + vcard_printf(vcards,"ADR;%s:%s", category_string, fields); + + g_free(fields); +} + +static void vcard_printf_datetime(GString *vcards, uint8_t format, + struct phonebook_contact *contact) +{ + const char *type; + char buf[LEN_MAX]; + + switch (contact->calltype) { + case CALL_TYPE_MISSED: + type = "MISSED"; + break; + + case CALL_TYPE_INCOMING: + type = "RECEIVED"; + break; + + case CALL_TYPE_OUTGOING: + type = "DIALED"; + break; + + case CALL_TYPE_NOT_A_CALL: + default: + return; + } + + if (select_qp_encoding(format, contact->datetime, NULL)) { + snprintf(buf, sizeof(buf), "X-IRMC-CALL-DATETIME;%s", type); + vcard_qp_print_encoded(vcards, buf, contact->datetime, NULL); + return; + } + + vcard_printf(vcards, "X-IRMC-CALL-DATETIME;%s:%s", type, + contact->datetime); +} + +static void vcard_printf_end(GString *vcards) +{ + vcard_printf(vcards, "END:VCARD"); +} + +void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact, + uint64_t filter, uint8_t format) +{ + if (format == FORMAT_VCARD30 && filter) + filter |= (FILTER_VERSION | FILTER_FN | FILTER_N | FILTER_TEL); + else if (format == FORMAT_VCARD21 && filter) + filter |= (FILTER_VERSION | FILTER_N | FILTER_TEL); + else + filter = (FILTER_VERSION | FILTER_UID | FILTER_N | FILTER_FN | + FILTER_TEL | FILTER_EMAIL | FILTER_ADR | + FILTER_BDAY | FILTER_NICKNAME | FILTER_URL | + FILTER_PHOTO | FILTER_ORG | FILTER_ROLE | + FILTER_TITLE | FILTER_X_IRMC_CALL_DATETIME); + + vcard_printf_begin(vcards, format); + + if (filter & FILTER_UID && *contact->uid) + vcard_printf_tag(vcards, format, "UID", NULL, contact->uid); + + if (filter & FILTER_N) + vcard_printf_name(vcards, format, contact); + + if (filter & FILTER_FN && (*contact->fullname || + format == FORMAT_VCARD30)) + vcard_printf_fullname(vcards, format, contact->fullname); + + if (filter & FILTER_TEL) { + GSList *l = contact->numbers; + + if (g_slist_length(l) == 0) + vcard_printf_number(vcards, format, NULL, 1, + TEL_TYPE_OTHER); + + for (; l; l = l->next) { + struct phonebook_field *number = l->data; + + vcard_printf_number(vcards, format, number->text, 1, + number->type); + } + } + + if (filter & FILTER_EMAIL) { + GSList *l = contact->emails; + + for (; l; l = l->next) { + struct phonebook_field *email = l->data; + vcard_printf_email(vcards, format, email->text, + email->type); + } + } + + if (filter & FILTER_ADR) { + GSList *l = contact->addresses; + + for (; l; l = l->next) { + struct phonebook_addr *addr = l->data; + vcard_printf_address(vcards, format, addr); + } + } + + if (filter & FILTER_BDAY && *contact->birthday) + vcard_printf_tag(vcards, format, "BDAY", NULL, + contact->birthday); + + if (filter & FILTER_NICKNAME && *contact->nickname) + vcard_printf_tag(vcards, format, "NICKNAME", NULL, + contact->nickname); + + if (filter & FILTER_URL) { + GSList *l = contact->urls; + + for (; l; l = l->next) { + struct phonebook_field *url = l->data; + vcard_printf_url(vcards, format, url->text, url->type); + } + } + + if (filter & FILTER_PHOTO && *contact->photo) + vcard_printf_tag(vcards, format, "PHOTO", NULL, + contact->photo); + + if (filter & FILTER_ORG) + vcard_printf_org(vcards, format, contact); + + if (filter & FILTER_ROLE && *contact->role) + vcard_printf_tag(vcards, format, "ROLE", NULL, contact->role); + + if (filter & FILTER_TITLE && *contact->title) + vcard_printf_tag(vcards, format, "TITLE", NULL, contact->title); + + if (filter & FILTER_X_IRMC_CALL_DATETIME) + vcard_printf_datetime(vcards, format, contact); + + vcard_printf_end(vcards); +} + +static void field_free(gpointer data) +{ + struct phonebook_field *field = data; + + g_free(field->text); + g_free(field); +} + +void phonebook_addr_free(gpointer addr) +{ + struct phonebook_addr *address = addr; + + g_slist_free_full(address->fields, g_free); + g_free(address); +} + +void phonebook_contact_free(struct phonebook_contact *contact) +{ + if (contact == NULL) + return; + + g_slist_free_full(contact->numbers, field_free); + g_slist_free_full(contact->emails, field_free); + g_slist_free_full(contact->addresses, phonebook_addr_free); + g_slist_free_full(contact->urls, field_free); + + g_free(contact->uid); + g_free(contact->fullname); + g_free(contact->given); + g_free(contact->family); + g_free(contact->additional); + g_free(contact->prefix); + g_free(contact->suffix); + g_free(contact->birthday); + g_free(contact->nickname); + g_free(contact->photo); + g_free(contact->company); + g_free(contact->department); + g_free(contact->role); + g_free(contact->title); + g_free(contact->datetime); + g_free(contact); +} |