4 * Copyright (C) 2008-2010 Intel Corporation. All rights reserved.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
37 #include "glib-helper.h"
39 #define ADDR_FIELD_AMOUNT 7
41 #define TYPE_INTERNATIONAL 145
43 #define PHONEBOOK_FLAG_CACHED 0x1
45 #define FILTER_VERSION (1 << 0)
46 #define FILTER_FN (1 << 1)
47 #define FILTER_N (1 << 2)
48 #define FILTER_PHOTO (1 << 3)
49 #define FILTER_BDAY (1 << 4)
50 #define FILTER_ADR (1 << 5)
51 #define FILTER_LABEL (1 << 6)
52 #define FILTER_TEL (1 << 7)
53 #define FILTER_EMAIL (1 << 8)
54 #define FILTER_MAILER (1 << 9)
55 #define FILTER_TZ (1 << 10)
56 #define FILTER_GEO (1 << 11)
57 #define FILTER_TITLE (1 << 12)
58 #define FILTER_ROLE (1 << 13)
59 #define FILTER_LOGO (1 << 14)
60 #define FILTER_AGENT (1 << 15)
61 #define FILTER_ORG (1 << 16)
62 #define FILTER_NOTE (1 << 17)
63 #define FILTER_REV (1 << 18)
64 #define FILTER_SOUND (1 << 19)
65 #define FILTER_URL (1 << 20)
66 #define FILTER_UID (1 << 21)
67 #define FILTER_KEY (1 << 22)
68 #define FILTER_NICKNAME (1 << 23)
69 #define FILTER_CATEGORIES (1 << 24)
70 #define FILTER_PROID (1 << 25)
71 #define FILTER_CLASS (1 << 26)
72 #define FILTER_SORT_STRING (1 << 27)
73 #define FILTER_X_IRMC_CALL_DATETIME (1 << 28)
75 #define FORMAT_VCARD21 0x00
76 #define FORMAT_VCARD30 0x01
78 #define QP_LINE_LEN 75
83 #define QP_SOFT_LINE_BREAK "="
84 #define QP_SELECT "\n!\"#$=@[\\]^`{|}~"
85 #define ASCII_LIMIT 0x7F
87 /* according to RFC 2425, the output string may need folding */
88 static void vcard_printf(GString *str, const char *fmt, ...)
92 int len_temp, line_number, i;
93 unsigned int line_delimit = 75;
96 vsnprintf(buf, sizeof(buf), fmt, ap);
99 line_number = strlen(buf) / line_delimit + 1;
101 for (i = 0; i < line_number; i++) {
102 len_temp = MIN(line_delimit, strlen(buf) - line_delimit * i);
103 g_string_append_len(str, buf + line_delimit * i, len_temp);
104 if (i != line_number - 1)
105 g_string_append(str, "\r\n ");
108 g_string_append(str, "\r\n");
111 /* According to RFC 2426, we need escape following characters:
112 * '\n', '\r', ';', ',', '\'.
114 static void add_slash(char *dest, const char *src, int len_max, int len)
118 for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) {
119 /* filling dest buffer - last field need to be reserved
123 if (j + 2 >= len_max)
124 /* not enough space in the buffer to put char
125 * preceded with escaping sequence (and '\0' in
133 if (j + 2 >= len_max)
142 if (j + 2 >= len_max)
156 static void escape_semicolon(char *dest, const char *src, int len_max, int len)
160 for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) {
162 if (j + 2 >= len_max)
174 static void set_escape(uint8_t format, char *dest, const char *src,
175 int len_max, int len)
177 if (format == FORMAT_VCARD30)
178 add_slash(dest, src, len_max, len);
179 else if (format == FORMAT_VCARD21)
180 escape_semicolon(dest, src, len_max, len);
183 static void get_escaped_fields(uint8_t format, char **fields, ...)
188 char escaped[LEN_MAX];
190 va_start(ap, fields);
191 line = g_string_new("");
193 for (field = va_arg(ap, char *); field; ) {
194 set_escape(format, escaped, field, LEN_MAX, strlen(field));
195 g_string_append(line, escaped);
197 field = va_arg(ap, char *);
200 g_string_append(line, ";");
205 *fields = g_string_free(line, FALSE);
208 static gboolean set_qp_encoding(char c)
212 if (strchr(QP_SELECT, q) != NULL)
215 if (q < '!' || q > '~')
221 static void append_qp_break_line(GString *vcards, size_t *limit)
223 /* Quoted Printable lines of text must be limited to less than 76
224 * characters and terminated by Quoted Printable softline break
225 * sequence of "=" (if some more characters left) */
226 g_string_append(vcards, QP_SOFT_LINE_BREAK);
227 g_string_append(vcards, "\r\n ");
228 *limit = QP_LINE_LEN - 1;
231 static void append_qp_ascii(GString *vcards, size_t *limit, char c)
234 append_qp_break_line(vcards, limit);
236 g_string_append_c(vcards, c);
240 static void append_qp_hex(GString *vcards, size_t *limit, char c)
242 if (*limit < QP_CHAR_LEN)
243 append_qp_break_line(vcards, limit);
245 g_string_append_printf(vcards, "=%2.2X", (unsigned char) c);
246 *limit -= QP_CHAR_LEN;
249 static void append_qp_new_line(GString *vcards, size_t *limit)
251 /* Multiple lines of text are separated with a Quoted Printable CRLF
252 * sequence of "=0D" followed by "=0A" followed by a Quoted Printable
253 * softline break sequence of "=" */
254 append_qp_hex(vcards, limit, QP_CR);
255 append_qp_hex(vcards, limit, QP_LF);
256 append_qp_break_line(vcards, limit);
259 static gboolean utf8_select(const char *field)
263 if (g_utf8_validate(field, -1, NULL) == FALSE)
266 for (pos = field; *pos != '\0'; pos = g_utf8_next_char(pos)) {
267 /* Test for non-standard UTF-8 character (out of range
268 * standard ASCII set), composed of more than single byte
269 * and represented by 32-bit value greater than 0x7F */
270 if (g_utf8_get_char(pos) > ASCII_LIMIT)
277 static void vcard_qp_print_encoded(GString *vcards, const char *desc, ...)
279 const char *field, *charset = "";
280 const char *encoding = ";ENCODING=QUOTED-PRINTABLE";
281 size_t limit, param_len;
286 for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) {
287 if (utf8_select(field) == TRUE) {
288 charset = ";CHARSET=UTF-8";
295 vcard_printf(vcards, "%s%s%s:", desc, encoding, charset);
296 g_string_truncate(vcards, vcards->len - 2);
298 param_len = strlen(desc) + strlen(encoding) + strlen(charset) + 1;
299 limit = QP_LINE_LEN - param_len;
303 for (field = va_arg(ap, char *); field != NULL; ) {
304 size_t i, size = strlen(field);
306 for (i = 0; i < size; ++i) {
307 if (set_qp_encoding(field[i])) {
308 if (field[i] == '\n') {
309 append_qp_new_line(vcards, &limit);
313 append_qp_hex(vcards, &limit, field[i]);
315 /* According to vCard 2.1 spec. semicolons in
316 * property parameter value must be escaped */
318 append_qp_hex(vcards, &limit, QP_ESC);
320 append_qp_ascii(vcards, &limit, field[i]);
324 field = va_arg(ap, char *);
326 append_qp_ascii(vcards, &limit, ';');
331 g_string_append(vcards, "\r\n");
334 static gboolean select_qp_encoding(uint8_t format, ...)
339 if (format != FORMAT_VCARD21)
342 va_start(ap, format);
344 for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) {
348 if (strpbrk(field, QP_SELECT)) {
353 /* Quoted Printable encoding is selected if there is
354 * a character, which value is out of range standard
355 * ASCII set, since it may be a part of some
356 * non-standard character such as specified by UTF-8 */
357 for (i = 0; (c = field[i]) != '\0'; ++i) {
358 if (c > ASCII_LIMIT) {
370 static void vcard_printf_begin(GString *vcards, uint8_t format)
372 vcard_printf(vcards, "BEGIN:VCARD");
374 if (format == FORMAT_VCARD30)
375 vcard_printf(vcards, "VERSION:3.0");
376 else if (format == FORMAT_VCARD21)
377 vcard_printf(vcards, "VERSION:2.1");
380 /* check if there is at least one contact field with personal data present */
381 static gboolean contact_fields_present(struct phonebook_contact * contact)
383 if (contact->family && strlen(contact->family) > 0)
386 if (contact->given && strlen(contact->given) > 0)
389 if (contact->additional && strlen(contact->additional) > 0)
392 if (contact->prefix && strlen(contact->prefix) > 0)
395 if (contact->suffix && strlen(contact->suffix) > 0)
398 /* none of the personal data fields are present*/
402 static void vcard_printf_name(GString *vcards, uint8_t format,
403 struct phonebook_contact *contact)
407 if (contact_fields_present(contact) == FALSE) {
408 /* If fields are empty, add only 'N:' as parameter.
409 * This is crucial for some devices (Nokia BH-903) which
410 * have problems with history listings and can't determine
411 * that a parameter is really empty if there are unnecessary
412 * characters after 'N:' (e.g. 'N:;;;;').
413 * We need to add only'N:' param - without semicolons.
415 vcard_printf(vcards, "N:");
419 if (select_qp_encoding(format, contact->family, contact->given,
420 contact->additional, contact->prefix,
421 contact->suffix, NULL)) {
422 vcard_qp_print_encoded(vcards, "N", contact->family,
423 contact->given, contact->additional,
424 contact->prefix, contact->suffix,
429 get_escaped_fields(format, &fields, contact->family,
430 contact->given, contact->additional,
431 contact->prefix, contact->suffix,
434 vcard_printf(vcards, "N:%s", fields);
439 static void vcard_printf_fullname(GString *vcards, uint8_t format,
444 if (!text || strlen(text) == 0) {
445 vcard_printf(vcards, "FN:");
449 if (select_qp_encoding(format, text, NULL)) {
450 vcard_qp_print_encoded(vcards, "FN", text, NULL);
454 set_escape(format, field, text, LEN_MAX, strlen(text));
455 vcard_printf(vcards, "FN:%s", field);
458 static void vcard_printf_number(GString *vcards, uint8_t format,
459 const char *number, int type,
460 enum phonebook_number_type category)
462 const char *intl = "", *category_string = "";
463 char buf[LEN_MAX], field[LEN_MAX];
465 /* TEL is a mandatory field, include even if empty */
466 if (!number || !strlen(number) || !type) {
467 vcard_printf(vcards, "TEL:");
473 if (format == FORMAT_VCARD21)
474 category_string = "HOME;VOICE";
475 else if (format == FORMAT_VCARD30)
476 category_string = "TYPE=HOME;TYPE=VOICE";
478 case TEL_TYPE_MOBILE:
479 if (format == FORMAT_VCARD21)
480 category_string = "CELL;VOICE";
481 else if (format == FORMAT_VCARD30)
482 category_string = "TYPE=CELL;TYPE=VOICE";
485 if (format == FORMAT_VCARD21)
486 category_string = "FAX";
487 else if (format == FORMAT_VCARD30)
488 category_string = "TYPE=FAX";
491 if (format == FORMAT_VCARD21)
492 category_string = "WORK;VOICE";
493 else if (format == FORMAT_VCARD30)
494 category_string = "TYPE=WORK;TYPE=VOICE";
497 if (format == FORMAT_VCARD21)
498 category_string = "OTHER;VOICE";
499 else if (format == FORMAT_VCARD30)
500 category_string = "TYPE=OTHER;TYPE=VOICE";
504 if ((type == TYPE_INTERNATIONAL) && (number[0] != '+'))
507 snprintf(field, sizeof(field), "%s%s", intl, number);
509 if (select_qp_encoding(format, number, NULL)) {
510 snprintf(buf, sizeof(buf), "TEL;%s", category_string);
511 vcard_qp_print_encoded(vcards, buf, field, NULL);
515 vcard_printf(vcards, "TEL;%s:%s", category_string, field);
518 static void vcard_printf_tag(GString *vcards, uint8_t format,
519 const char *tag, const char *category,
523 char *separator = "", *type = "";
524 char buf[LEN_MAX], field[LEN_MAX];
526 if (tag == NULL || strlen(tag) == 0)
529 if (fld == NULL || (len = strlen(fld)) == 0) {
530 vcard_printf(vcards, "%s:", tag);
534 if (category && strlen(category)) {
536 if (format == FORMAT_VCARD30)
542 snprintf(buf, LEN_MAX, "%s%s%s%s", tag, separator, type, category);
544 if (select_qp_encoding(format, fld, NULL)) {
545 vcard_qp_print_encoded(vcards, buf, fld, NULL);
549 set_escape(format, field, fld, LEN_MAX, len);
550 vcard_printf(vcards, "%s:%s", buf, field);
553 static void vcard_printf_email(GString *vcards, uint8_t format,
555 enum phonebook_field_type category)
557 const char *category_string = "";
558 char buf[LEN_MAX], field[LEN_MAX];
561 if (!address || !(len = strlen(address))) {
562 vcard_printf(vcards, "EMAIL:");
566 case FIELD_TYPE_HOME:
567 if (format == FORMAT_VCARD21)
568 category_string = "INTERNET;HOME";
569 else if (format == FORMAT_VCARD30)
570 category_string = "TYPE=INTERNET;TYPE=HOME";
572 case FIELD_TYPE_WORK:
573 if (format == FORMAT_VCARD21)
574 category_string = "INTERNET;WORK";
575 else if (format == FORMAT_VCARD30)
576 category_string = "TYPE=INTERNET;TYPE=WORK";
579 if (format == FORMAT_VCARD21)
580 category_string = "INTERNET";
581 else if (format == FORMAT_VCARD30)
582 category_string = "TYPE=INTERNET;TYPE=OTHER";
585 if (select_qp_encoding(format, address, NULL)) {
586 snprintf(buf, sizeof(buf), "EMAIL;%s", category_string);
587 vcard_qp_print_encoded(vcards, buf, address, NULL);
591 set_escape(format, field, address, LEN_MAX, len);
592 vcard_printf(vcards, "EMAIL;%s:%s", category_string, field);
595 static void vcard_printf_url(GString *vcards, uint8_t format,
597 enum phonebook_field_type category)
599 const char *category_string = "";
600 char buf[LEN_MAX], field[LEN_MAX];
602 if (!url || strlen(url) == 0) {
603 vcard_printf(vcards, "URL:");
608 case FIELD_TYPE_HOME:
609 if (format == FORMAT_VCARD21)
610 category_string = "INTERNET;HOME";
611 else if (format == FORMAT_VCARD30)
612 category_string = "TYPE=INTERNET;TYPE=HOME";
614 case FIELD_TYPE_WORK:
615 if (format == FORMAT_VCARD21)
616 category_string = "INTERNET;WORK";
617 else if (format == FORMAT_VCARD30)
618 category_string = "TYPE=INTERNET;TYPE=WORK";
621 if (format == FORMAT_VCARD21)
622 category_string = "INTERNET";
623 else if (format == FORMAT_VCARD30)
624 category_string = "TYPE=INTERNET";
628 if (select_qp_encoding(format, url, NULL)) {
629 snprintf(buf, sizeof(buf), "URL;%s", category_string);
630 vcard_qp_print_encoded(vcards, buf, url, NULL);
634 set_escape(format, field, url, LEN_MAX, strlen(url));
635 vcard_printf(vcards, "URL;%s:%s", category_string, field);
638 static gboolean org_fields_present(struct phonebook_contact *contact)
640 if (contact->company && strlen(contact->company))
643 if (contact->department && strlen(contact->department))
649 static void vcard_printf_org(GString *vcards, uint8_t format,
650 struct phonebook_contact *contact)
654 if (org_fields_present(contact) == FALSE)
657 if (select_qp_encoding(format, contact->company,
658 contact->department, NULL)) {
659 vcard_qp_print_encoded(vcards, "ORG", contact->company,
660 contact->department, NULL);
664 get_escaped_fields(format, &fields, contact->company,
665 contact->department, NULL);
667 vcard_printf(vcards, "ORG:%s", fields);
672 static void vcard_printf_address(GString *vcards, uint8_t format,
673 struct phonebook_addr *address)
675 char *fields, field_esc[LEN_MAX];
676 const char *category_string = "";
677 char buf[LEN_MAX], *address_fields[ADDR_FIELD_AMOUNT];
683 vcard_printf(vcards, "ADR:");
687 switch (address->type) {
688 case FIELD_TYPE_HOME:
689 if (format == FORMAT_VCARD21)
690 category_string = "HOME";
691 else if (format == FORMAT_VCARD30)
692 category_string = "TYPE=HOME";
694 case FIELD_TYPE_WORK:
695 if (format == FORMAT_VCARD21)
696 category_string = "WORK";
697 else if (format == FORMAT_VCARD30)
698 category_string = "TYPE=WORK";
701 if (format == FORMAT_VCARD21)
702 category_string = "OTHER";
703 else if (format == FORMAT_VCARD30)
704 category_string = "TYPE=OTHER";
708 for (i = 0, l = address->fields; l; l = l->next)
709 address_fields[i++] = l->data;
711 if (select_qp_encoding(format, address_fields[0], address_fields[1],
712 address_fields[2], address_fields[3],
713 address_fields[4], address_fields[5],
714 address_fields[6], NULL)) {
715 snprintf(buf, sizeof(buf), "ADR;%s", category_string);
716 vcard_qp_print_encoded(vcards, buf,
717 address_fields[0], address_fields[1],
718 address_fields[2], address_fields[3],
719 address_fields[4], address_fields[5],
720 address_fields[6], NULL);
724 /* allocate enough memory to insert address fields separated by ';'
725 * and terminated by '\0' */
726 len = ADDR_FIELD_AMOUNT * LEN_MAX;
727 fields = g_malloc0(len);
729 for (l = address->fields; l; l = l->next) {
730 char *field = l->data;
733 set_escape(format, field_esc, field, LEN_MAX,
735 g_strlcat(fields, field_esc, len);
739 /* not adding ';' after last addr field */
740 g_strlcat(fields, ";", len);
743 vcard_printf(vcards,"ADR;%s:%s", category_string, fields);
748 static void vcard_printf_datetime(GString *vcards, uint8_t format,
749 struct phonebook_contact *contact)
754 switch (contact->calltype) {
755 case CALL_TYPE_MISSED:
759 case CALL_TYPE_INCOMING:
763 case CALL_TYPE_OUTGOING:
767 case CALL_TYPE_NOT_A_CALL:
772 if (select_qp_encoding(format, contact->datetime, NULL)) {
773 snprintf(buf, sizeof(buf), "X-IRMC-CALL-DATETIME;%s", type);
774 vcard_qp_print_encoded(vcards, buf, contact->datetime, NULL);
778 vcard_printf(vcards, "X-IRMC-CALL-DATETIME;%s:%s", type,
782 static void vcard_printf_end(GString *vcards)
784 vcard_printf(vcards, "END:VCARD");
787 void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact,
788 uint64_t filter, uint8_t format)
790 if (format == FORMAT_VCARD30 && filter)
791 filter |= (FILTER_VERSION | FILTER_FN | FILTER_N | FILTER_TEL);
792 else if (format == FORMAT_VCARD21 && filter)
793 filter |= (FILTER_VERSION | FILTER_N | FILTER_TEL);
795 filter = (FILTER_VERSION | FILTER_UID | FILTER_N | FILTER_FN |
796 FILTER_TEL | FILTER_EMAIL | FILTER_ADR |
797 FILTER_BDAY | FILTER_NICKNAME | FILTER_URL |
798 FILTER_PHOTO | FILTER_ORG | FILTER_ROLE |
799 FILTER_TITLE | FILTER_X_IRMC_CALL_DATETIME);
801 vcard_printf_begin(vcards, format);
803 if (filter & FILTER_UID && *contact->uid)
804 vcard_printf_tag(vcards, format, "UID", NULL, contact->uid);
806 if (filter & FILTER_N)
807 vcard_printf_name(vcards, format, contact);
809 if (filter & FILTER_FN && (*contact->fullname ||
810 format == FORMAT_VCARD30))
811 vcard_printf_fullname(vcards, format, contact->fullname);
813 if (filter & FILTER_TEL) {
814 GSList *l = contact->numbers;
816 if (g_slist_length(l) == 0)
817 vcard_printf_number(vcards, format, NULL, 1,
820 for (; l; l = l->next) {
821 struct phonebook_field *number = l->data;
823 vcard_printf_number(vcards, format, number->text, 1,
828 if (filter & FILTER_EMAIL) {
829 GSList *l = contact->emails;
831 for (; l; l = l->next) {
832 struct phonebook_field *email = l->data;
833 vcard_printf_email(vcards, format, email->text,
838 if (filter & FILTER_ADR) {
839 GSList *l = contact->addresses;
841 for (; l; l = l->next) {
842 struct phonebook_addr *addr = l->data;
843 vcard_printf_address(vcards, format, addr);
847 if (filter & FILTER_BDAY && *contact->birthday)
848 vcard_printf_tag(vcards, format, "BDAY", NULL,
851 if (filter & FILTER_NICKNAME && *contact->nickname)
852 vcard_printf_tag(vcards, format, "NICKNAME", NULL,
855 if (filter & FILTER_URL) {
856 GSList *l = contact->urls;
858 for (; l; l = l->next) {
859 struct phonebook_field *url = l->data;
860 vcard_printf_url(vcards, format, url->text, url->type);
864 if (filter & FILTER_PHOTO && *contact->photo)
865 vcard_printf_tag(vcards, format, "PHOTO", NULL,
868 if (filter & FILTER_ORG)
869 vcard_printf_org(vcards, format, contact);
871 if (filter & FILTER_ROLE && *contact->role)
872 vcard_printf_tag(vcards, format, "ROLE", NULL, contact->role);
874 if (filter & FILTER_TITLE && *contact->title)
875 vcard_printf_tag(vcards, format, "TITLE", NULL, contact->title);
877 if (filter & FILTER_X_IRMC_CALL_DATETIME)
878 vcard_printf_datetime(vcards, format, contact);
880 vcard_printf_end(vcards);
883 static void field_free(gpointer data)
885 struct phonebook_field *field = data;
891 void phonebook_addr_free(gpointer addr)
893 struct phonebook_addr *address = addr;
895 g_slist_free_full(address->fields, g_free);
899 void phonebook_contact_free(struct phonebook_contact *contact)
904 g_slist_free_full(contact->numbers, field_free);
905 g_slist_free_full(contact->emails, field_free);
906 g_slist_free_full(contact->addresses, phonebook_addr_free);
907 g_slist_free_full(contact->urls, field_free);
909 g_free(contact->uid);
910 g_free(contact->fullname);
911 g_free(contact->given);
912 g_free(contact->family);
913 g_free(contact->additional);
914 g_free(contact->prefix);
915 g_free(contact->suffix);
916 g_free(contact->birthday);
917 g_free(contact->nickname);
918 g_free(contact->photo);
919 g_free(contact->company);
920 g_free(contact->department);
921 g_free(contact->role);
922 g_free(contact->title);
923 g_free(contact->datetime);