diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c
index b694438..ccab946 100644
--- a/obexd/plugins/vcard.c
+++ b/obexd/plugins/vcard.c
#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!\"#$=@[\\]^`{|}~"
+
/* according to RFC 2425, the output string may need folding */
static void vcard_printf(GString *str, const char *fmt, ...)
{
*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 void vcard_qp_print_encoded(GString *vcards, const char *desc, ...)
+{
+ char *field;
+ va_list ap;
+
+ vcard_printf(vcards, "%s;ENCODING=QUOTED-PRINTABLE:", desc);
+ g_string_truncate(vcards, vcards->len - 2);
+
+ va_start(ap, desc);
+
+ for (field = va_arg(ap, char *); field != NULL; ) {
+ size_t i, limit = QP_LINE_LEN, 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 *)) {
+ if (strpbrk(field, QP_SELECT)) {
+ 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");
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,
const char *text)
{
char field[LEN_MAX];
+
+ 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);
}
enum phonebook_number_type category)
{
const char *intl = "", *category_string = "";
- char buf[128];
+ char buf[128], field[LEN_MAX];
/* TEL is a mandatory field, include even if empty */
if (!number || !strlen(number) || !type) {
if ((type == TYPE_INTERNATIONAL) && (number[0] != '+'))
intl = "+";
+ if (select_qp_encoding(format, number, NULL)) {
+ snprintf(buf, sizeof(buf), "TEL;%s", category_string);
+ snprintf(field, sizeof(field), "%s%s", intl, number);
+ vcard_qp_print_encoded(vcards, buf, field, NULL);
+ return;
+ }
+
snprintf(buf, sizeof(buf), "TEL;%s:%s\%s", category_string,
intl, number);
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);
}
enum phonebook_field_type category)
{
const char *category_string = "";
- char field[LEN_MAX];
+ char buf[LEN_MAX], field[LEN_MAX];
int len = 0;
if (!address || !(len = strlen(address))) {
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);
}
enum phonebook_field_type category)
{
const char *category_string = "";
- char field[LEN_MAX];
+ char buf[LEN_MAX], field[LEN_MAX];
if (!url || strlen(url) == 0) {
vcard_printf(vcards, "URL:");
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);
}
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);
{
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;
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;
struct phonebook_contact *contact)
{
const char *type;
+ char buf[LEN_MAX];
switch (contact->calltype) {
case CALL_TYPE_MISSED:
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);
}