diff options
author | Milan Crha <mcrha@redhat.com> | 2013-10-10 19:08:20 +0200 |
---|---|---|
committer | Milan Crha <mcrha@redhat.com> | 2013-10-10 19:09:19 +0200 |
commit | a79ca0954686837335576da51f13fc4215fc0d47 (patch) | |
tree | 037c8cf9a61511f0926a53952e486a49bfd8ed7e | |
parent | 496e2b2cc13c09d98095c39bb42bdc1bc917c3fe (diff) | |
download | evolution-data-server-a79ca0954686837335576da51f13fc4215fc0d47.tar.gz |
Bug #695232 - Finish EVCard quoted-printable handling
-rw-r--r-- | addressbook/libebook-contacts/e-vcard.c | 137 | ||||
-rw-r--r-- | tests/libebook-contacts/test-vcard-parsing.c | 159 |
2 files changed, 277 insertions, 19 deletions
diff --git a/addressbook/libebook-contacts/e-vcard.c b/addressbook/libebook-contacts/e-vcard.c index 2654690cc..f6d521892 100644 --- a/addressbook/libebook-contacts/e-vcard.c +++ b/addressbook/libebook-contacts/e-vcard.c @@ -254,27 +254,30 @@ read_attribute_value (EVCardAttribute *attr, if (*lp == '=' && quoted_printable) { gchar a, b; - if ((a = *(++lp)) == '\0') break; - if ((b = *(++lp)) == '\0') break; + + /* it's for the '=' */ + lp++; + lp = skip_newline (lp, quoted_printable); + + if ((a = *(lp++)) == '\0') break; + lp = skip_newline (lp, quoted_printable); + + if ((b = *(lp++)) == '\0') break; if (isxdigit (a) && isxdigit (b)) { gchar c; a = tolower (a); b = tolower (b); - c = (((a >= 'a' ? a - 'a' + 10 : a - '0') &0x0f) << 4) - | ((b >= 'a' ? b - 'a' + 10 : b - '0') &0x0f); + c = (((a >= 'a' ? a - 'a' + 10 : a - '0') & 0x0f) << 4) + | ((b >= 'a' ? b - 'a' + 10 : b - '0') & 0x0f); g_string_append_c (str, c); /* add decoded byte (this is not a unicode yet) */ + } else { + g_string_append_c (str, '='); + g_string_append_c (str, a); + g_string_append_c (str, b); } - else - { - g_string_append_c (str, a); - g_string_append_c (str, b); - } - - lp++; - } else if (*lp == '\\') { /* convert back to the non-escaped version of * the characters */ @@ -919,7 +922,8 @@ e_vcard_new_from_string (const gchar *str) } static gchar * -e_vcard_qp_encode (const gchar *txt) +e_vcard_qp_encode (const gchar *txt, + gboolean can_wrap) { const gchar *p = txt; GString *escaped = g_string_new (""); @@ -927,7 +931,7 @@ e_vcard_qp_encode (const gchar *txt) while (*p != '\0') { if ((*p >= 33 && *p <= 60) || (*p >= 62 && *p <= 126)) { - if (count == 75) { + if (can_wrap && count == 75) { g_string_append (escaped, "=" CRLF " "); count = 1; /* 1 for space needed for folding */ } @@ -938,7 +942,7 @@ e_vcard_qp_encode (const gchar *txt) continue; } - if (count >= 73) { + if (count >= 73 && can_wrap) { g_string_append (escaped, "=" CRLF " "); count = 1; /* 1 for space needed for folding */ } @@ -950,6 +954,52 @@ e_vcard_qp_encode (const gchar *txt) return g_string_free (escaped, FALSE); } +static gchar * +e_vcard_qp_decode (const gchar *txt) +{ + const gchar *inptr; + gchar *decoded, *outptr; + + if (!txt) + return NULL; + + decoded = g_malloc (sizeof (gchar) * strlen (txt) + 1); + + outptr = decoded; + + for (inptr = txt; *inptr; inptr++) { + gchar c = *inptr; + + if (c == '=' && (inptr[1] == '\r' || inptr[1] == '\n')) { + /* soft line-break */ + if (inptr[2] == '\n') + inptr++; + inptr++; + continue; + } + + if (c == '=' && inptr[1] && inptr[2]) { + guchar a = toupper (inptr[1]), b = toupper (inptr[2]); + if (isxdigit (a) && isxdigit (b)) { + *outptr++ = (((a >= 'A' ? a - 'A' + 10 : a - '0') & 0x0f) << 4) + | ((b >= 'A' ? b - 'A' + 10 : b - '0') & 0x0f); + } else { + *outptr++ = '='; + *outptr++ = inptr[1]; + *outptr++ = inptr[2]; + } + + inptr += 2; + } else { + *outptr++ = *inptr; + } + } + + *outptr = '\0'; + + return decoded; +} + static GHashTable * generate_dict_validator (const gchar *words) { @@ -1075,7 +1125,7 @@ e_vcard_to_string_vcard_21 (EVCard *evc) if (encode) { gchar *escaped_value; - escaped_value = e_vcard_qp_encode (value); + escaped_value = e_vcard_qp_encode (value, TRUE); g_string_append (attr_str, escaped_value); g_free (escaped_value); @@ -1121,6 +1171,7 @@ e_vcard_to_string_vcard_30 (EVCard *evc) EVCardAttribute *attr = l->data; GString *attr_str; glong len; + EVCardAttributeParam *quoted_printable_param = NULL; if (!g_ascii_strcasecmp (attr->name, "VERSION")) continue; @@ -1141,6 +1192,18 @@ e_vcard_to_string_vcard_30 (EVCard *evc) /* handle the parameters */ for (list = attr->params; list; list = list->next) { EVCardAttributeParam *param = list->data; + + /* quoted-printable encoding was eliminated in 3.0, + thus decode the value before saving and remove the param later */ + if (!quoted_printable_param && + param->values && param->values->data && !param->values->next && + g_ascii_strcasecmp (param->name, "ENCODING") == 0 && + g_ascii_strcasecmp (param->values->data, "quoted-printable") == 0) { + quoted_printable_param = param; + /* do not store it */ + continue; + } + /* 5.8.2: * param = param-name "=" param-value *("," param-value) */ @@ -1148,6 +1211,7 @@ e_vcard_to_string_vcard_30 (EVCard *evc) g_string_append (attr_str, param->name); if (param->values) { g_string_append_c (attr_str, '='); + for (v = param->values; v; v = v->next) { gchar *value = v->data; gchar *pval = value; @@ -1189,6 +1253,19 @@ e_vcard_to_string_vcard_30 (EVCard *evc) gchar *value = v->data; gchar *escaped_value = NULL; + /* values are in quoted-printable encoding, but this cannot be used in vCard 3.0, + thus it needs to be converted first */ + if (quoted_printable_param) { + gchar *qp_decoded; + + qp_decoded = e_vcard_qp_decode (value); + + /* replace the actual value with the decoded */ + g_free (value); + value = qp_decoded; + v->data = value; + } + escaped_value = e_vcard_escape_string (value); g_string_append (attr_str, escaped_value); @@ -1234,6 +1311,10 @@ e_vcard_to_string_vcard_30 (EVCard *evc) g_string_append (str, attr_str->str); g_string_free (attr_str, TRUE); + + /* remove the encoding parameter, to not decode multiple times */ + if (quoted_printable_param) + e_vcard_attribute_remove_param (attr, quoted_printable_param->name); } g_string_append (str, "END:VCARD"); @@ -1684,10 +1765,21 @@ e_vcard_attribute_add_value_decoded (EVCardAttribute *attr, attr->decoded_values = g_list_append (attr->decoded_values, decoded); break; } - case EVC_ENCODING_QP: - g_warning ("need to implement quoted printable decoding"); + case EVC_ENCODING_QP: { + GString *decoded = g_string_new_len (value, len); + gchar *qp_data = e_vcard_qp_encode (decoded->str, FALSE); + + /* make sure the decoded list is up to date */ + e_vcard_attribute_get_values_decoded (attr); + + d (printf ("qp encoded value: %s\n", qp_data)); + d (printf ("original length: %d\n", len)); + + attr->values = g_list_append (attr->values, qp_data); + attr->decoded_values = g_list_append (attr->decoded_values, decoded); break; } + } } /** @@ -2319,7 +2411,14 @@ e_vcard_attribute_get_values_decoded (EVCardAttribute *attr) attr->decoded_values = g_list_reverse (attr->decoded_values); break; case EVC_ENCODING_QP: - g_warning ("need to implement quoted printable decoding"); + for (l = attr->values; l; l = l->next) { + gchar *decoded; + + decoded = e_vcard_qp_decode (l->data); + attr->decoded_values = g_list_prepend (attr->decoded_values, g_string_new (decoded)); + g_free (decoded); + } + attr->decoded_values = g_list_reverse (attr->decoded_values); break; } } diff --git a/tests/libebook-contacts/test-vcard-parsing.c b/tests/libebook-contacts/test-vcard-parsing.c index bd85f8731..c32ee04a7 100644 --- a/tests/libebook-contacts/test-vcard-parsing.c +++ b/tests/libebook-contacts/test-vcard-parsing.c @@ -1,4 +1,5 @@ #include <libebook/libebook.h> +#include <string.h> static gboolean compare_single_value (EVCard *vcard, @@ -219,6 +220,163 @@ test_econtact (const gchar *vcard_str) return TRUE; } +static gboolean +test_vcard_qp_2_1_parsing (const gchar *vcard_str, + const gchar *expected_text) +{ + EVCard *vcard; + EVCardAttribute *attr; + gchar *value; + + vcard = e_vcard_new_from_string (vcard_str); + g_return_val_if_fail (vcard != NULL, FALSE); + + attr = e_vcard_get_attribute (vcard, "FN"); + g_return_val_if_fail (attr != NULL, FALSE); + + value = e_vcard_attribute_get_value (attr); + g_return_val_if_fail (value != NULL, FALSE); + + g_return_val_if_fail (g_strcmp0 (value, expected_text) == 0, FALSE); + + g_object_unref (vcard); + g_free (value); + + return TRUE; +} + +static gboolean +test_vcard_qp_2_1_saving (const gchar *expected_text) +{ + EVCard *vcard; + EVCardAttribute *attr; + EVCardAttributeParam *param; + gchar *str, *encoded_value; + GString *decoded; + + vcard = e_vcard_new (); + attr = e_vcard_attribute_new (NULL, "FN"); + param = e_vcard_attribute_param_new ("ENCODING"); + e_vcard_attribute_param_add_value (param, "quoted-printable"); + e_vcard_attribute_add_param (attr, param); + + e_vcard_attribute_add_value_decoded (attr, expected_text, strlen (expected_text)); + + decoded = e_vcard_attribute_get_value_decoded (attr); + g_return_val_if_fail (decoded != NULL, FALSE); + g_return_val_if_fail (g_strcmp0 (decoded->str, expected_text) == 0, FALSE); + g_string_free (decoded, TRUE); + + encoded_value = e_vcard_attribute_get_value (attr); + g_return_val_if_fail (encoded_value != NULL, FALSE); + /* it's the encoded value, thus it cannot match */ + g_return_val_if_fail (g_strcmp0 (encoded_value, expected_text) != 0, FALSE); + g_free (encoded_value); + + e_vcard_add_attribute (vcard, attr); + + str = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_21); + g_object_unref (vcard); + + g_return_val_if_fail (str != NULL, FALSE); + + vcard = e_vcard_new_from_string (str); + g_free (str); + + g_return_val_if_fail (vcard != NULL, FALSE); + + attr = e_vcard_get_attribute (vcard, "FN"); + g_return_val_if_fail (attr != NULL, FALSE); + + decoded = e_vcard_attribute_get_value_decoded (attr); + g_return_val_if_fail (decoded != NULL, FALSE); + g_return_val_if_fail (g_strcmp0 (decoded->str, expected_text) == 0, FALSE); + g_string_free (decoded, TRUE); + + g_object_unref (vcard); + + return TRUE; +} + +static gboolean +test_vcard_qp_3_0_saving (const gchar *expected_text) +{ + EVCard *vcard; + EVCardAttribute *attr; + EVCardAttributeParam *param; + gchar *str, *value, *encoded_value; + GString *decoded; + + vcard = e_vcard_new (); + attr = e_vcard_attribute_new (NULL, "FN"); + param = e_vcard_attribute_param_new ("ENCODING"); + e_vcard_attribute_param_add_value (param, "quoted-printable"); + e_vcard_attribute_add_param (attr, param); + + e_vcard_attribute_add_value_decoded (attr, expected_text, strlen (expected_text)); + e_vcard_add_attribute (vcard, attr); + + decoded = e_vcard_attribute_get_value_decoded (attr); + g_return_val_if_fail (decoded != NULL, FALSE); + g_return_val_if_fail (g_strcmp0 (decoded->str, expected_text) == 0, FALSE); + g_string_free (decoded, TRUE); + + encoded_value = e_vcard_attribute_get_value (attr); + g_return_val_if_fail (encoded_value != NULL, FALSE); + /* it's the encoded value, thus it cannot match */ + g_return_val_if_fail (g_strcmp0 (encoded_value, expected_text) != 0, FALSE); + + str = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30); + + g_object_unref (vcard); + + g_return_val_if_fail (str != NULL, FALSE); + + vcard = e_vcard_new_from_string (str); + g_free (str); + + g_return_val_if_fail (vcard != NULL, FALSE); + + attr = e_vcard_get_attribute (vcard, "FN"); + g_return_val_if_fail (attr != NULL, FALSE); + + decoded = e_vcard_attribute_get_value_decoded (attr); + g_return_val_if_fail (decoded != NULL, FALSE); + g_return_val_if_fail (g_strcmp0 (decoded->str, expected_text) == 0, FALSE); + g_string_free (decoded, TRUE); + + value = e_vcard_attribute_get_value (attr); + g_return_val_if_fail (value != NULL, FALSE); + /* either base64 or a free form, but not quoted-printable for sure */ + g_return_val_if_fail (g_strcmp0 (value, encoded_value) != 0, FALSE); + g_free (value); + + g_object_unref (vcard); + g_free (encoded_value); + + return TRUE; +} + +static void +test_vcard_quoted_printable (void) +{ + const gchar *expected_text = "ActualValue ěščřžýáíéúůóöĚŠČŘŽÝÁÍÉÚŮÓÖ§ " + "1234567890 1234567890 1234567890 1234567890 1234567890"; + const gchar *vcard_2_1_str = + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "FN;ENCODING=quoted-printable:ActualValue=20=C4=9B=C5=A1" + "=C4=8D=C5=99=C5=BE=C3=BD=C3=A1=C3=AD=C3=A9=C3=BA=C5=AF=C3" + "=B3=C3=B6=C4=9A=C5=A0=C4=8C=C5=98=C5=BD=C3=9D=C3=81=C3=8D" + "=C3=89=C3=9A=C5=AE=C3=93=C3=96=C2=A7=201234567890=2012345" + "67890=201234567890=201234567890=201234567890\r\n" + "END:VCARD\r\n"; + + g_assert (test_vcard_qp_2_1_parsing (vcard_2_1_str, expected_text)); + g_assert (test_vcard_qp_2_1_saving (expected_text)); + g_assert (test_vcard_qp_3_0_saving (expected_text)); +} + static const gchar *test_vcard_no_uid_str = "BEGIN:VCARD\r\n" "VERSION:3.0\r\n" @@ -273,6 +431,7 @@ main (gint argc, g_test_add_func ("/Parsing/VCard/WithoutUID", test_vcard_without_uid); g_test_add_func ("/Parsing/VCard/WithUID", test_contact_with_uid); g_test_add_func ("/Parsing/VCard/WithoutUID", test_contact_without_uid); + g_test_add_func ("/Parsing/VCard/QuotedPrintable", test_vcard_quoted_printable); return g_test_run (); } |