/* * vcard-parser.c * * Copyright 2019 Michael Rasmussen * * 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 Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include #ifdef HAVE_CONFIG_H # include "config.h" #endif static gchar* Properties[VCARD_PROPERTIES+1] = { "N", "FN", "PHOTO", "BDAY", "ADR", "LABEL", "TEL", "EMAIL", "MAILER", "GEO", "TITLE", "ROLE", "LOGO", "ORG", "NOTE", "REV", "SOUND", "URL", "UID", "VERSION", "KEY", "TZ", "SOURCE", "AGENT", "PROFILE", "CATEGORIES", "SORT-STRING", "PRODID", "NICKNAME", "NAME", "CLASS", "FBURL", "CAPURI", "CALURI", "CALADRURI", "IMPP", "XML", "ANNIVERSARY", "CLIENTPIDMAP", "LANG", "GENDER", "KIND", "MEMBER", "RELATED", "BIRTHPLACE", "DEATHPLACE", "DEATHDATE", "EXPERTISE", "HOBBY", "INTEREST", "ORG-DIRECTORY", NULL }; static void vcard_property_free(gpointer data) { if (!data) return; VCardProperty* vp = (VCardProperty*) data; g_free(vp->name); g_free(vp->value); g_free(vp); } static Property get_property(const gchar* property) { for (Property i = N; i < VCARD_PROPERTIES; i++) { if (g_strcmp0(Properties[i], property) == 0) return i; } return VCARD_PROPERTIES; } static gboolean check_support(Property property, VCardVersion version) { gboolean support = FALSE; if (version == VCARD_VERSION_2_1) { support = property <= TZ ? TRUE : FALSE; } else if (version == VCARD_VERSION_3_0) { support = property <= IMPP ? TRUE : FALSE; } else if (version == VCARD_VERSION_4_0) { support = property <= VCARD_PROPERTIES ? TRUE : FALSE; } return support; } static VCardParserResponse has_required_version(const gchar** buf, VCardVersion* version) { const gchar** tmp = buf; gboolean found = FALSE; VCardParserResponse result = VCARD_PARSER_VERSION_MISSING; while (*tmp && !found) { if (!g_str_has_prefix(*tmp, "VERSION")) { tmp++; } else { found = TRUE; gchar** b = g_strsplit(*tmp, ":", 0); if (g_strv_length(b) == 2) { if (*version == VCARD_VERSION_DETECT) { *version = str_2_vcard_version(b[1]); result = VCARD_PARSER_OK; } else { if (*version == str_2_vcard_version(b[1])) { result = VCARD_PARSER_OK; } else result = VCARD_PARSER_VERSION_MISMATCH; } } g_strfreev(b); } } return result; } static gchar** vcard_text_normalize(const gchar** text) { gchar** new = NULL; const gchar** tmp = text; int count = 0; gchar* str = NULL; if (text == NULL) return NULL; new = g_new0(gchar*, g_strv_length((gchar **) text) + 1); while (*tmp && g_strcmp0(*tmp, "") != 0) { if (*tmp[0] == ' ') { // Multi line value if (str) { gchar* s = g_strdup(str); g_free(str); str = g_strconcat(s, "\n", *tmp+1, NULL); } else str = g_strdup(*tmp+1); } else { if (str) { new[count++] = g_strdup(str); g_free(str); str = NULL; } str = g_strdup(*tmp); } tmp++; } if (str) { new[count++] = g_strdup(str); g_free(str); } return new; } static VCardParserResponse vcard_parse(gchar** text, VCardVersion version, GHashTable** vcard) { VCardParserResponse r = VCARD_PARSER_ERROR; guint l = g_strv_length(text); gboolean support; for (guint i = 0; i < l; i++) { gchar** b = g_strsplit(text[i], ":", 2); if (g_strv_length(b) != 2 || !b[0] || !b[1]) { destroy_hash_table(*vcard); *vcard = NULL; g_strfreev(b); return VCARD_PARSER_ERROR; } gchar** c = g_strsplit(b[0], ";", 0); if (g_strcmp0(c[0], "BEGIN") == 0 || g_strcmp0(c[0], "VERSION") == 0 || g_strcmp0(c[0], "END") == 0) { g_strfreev(c); g_strfreev(b); continue; } if (g_str_has_prefix(c[0], "X-")) { support = TRUE; } else { Property property = get_property(c[0]); support = check_support(property, version); } if (support) { GSList* value = g_hash_table_lookup(*vcard, c[0]); VCardProperty* vp = g_new(VCardProperty, 1); vp->name = g_strdup(b[0]); vp->value = g_strdup(b[1]); value = g_slist_append(value, vp); g_hash_table_insert(*vcard, g_strdup(c[0]), value); } else { destroy_hash_table(*vcard); *vcard = NULL; g_strfreev(c); g_strfreev(b); return VCARD_PARSER_ATTRIBUTE_VERSION_MISMATCH; } g_strfreev(c); g_strfreev(b); } r = VCARD_PARSER_OK; return r; } Property vcard_max_property(VCardVersion version) { Property p = VCARD_PROPERTIES; if (version == VCARD_VERSION_2_1) { p = TZ; } else if (version == VCARD_VERSION_3_0) { p = IMPP; } else if (version == VCARD_VERSION_4_0) { p = VCARD_PROPERTIES - 1; } return p; } void destroy_hash_table(GHashTable* ht) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, ht); while (g_hash_table_iter_next(&iter, &key, &value)) { if (value) g_slist_free_full((GSList *) value, vcard_property_free); } g_hash_table_destroy(ht); } void init_g_hash_table(GHashTable** ht, VCardVersion version) { if (!ht) return; Property property = vcard_max_property(version); if (property < VCARD_PROPERTIES) { *ht = g_hash_table_new_full( g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL); for (Property i = N; i <= property; i++) { g_hash_table_insert(*ht, g_strdup(Properties[i]), NULL); } } } VCardVersion str_2_vcard_version(const gchar* version) { VCardVersion vv = VCARD_VERSION_DETECT; if (version) { if (g_strcmp0("2.1", version) == 0) vv = VCARD_VERSION_2_1; else if (g_strcmp0("3.0", version) == 0) vv = VCARD_VERSION_3_0; else if (g_strcmp0("4.0", version) == 0) vv = VCARD_VERSION_4_0; else vv = VCARD_VERSION_UNSUPPORTED; } return vv; } gchar* vcard_version_2_str(VCardVersion version) { gchar* vv = NULL; if (VCARD_VERSION_2_1 == version) vv = g_strdup("2.1"); else if (VCARD_VERSION_3_0 == version) vv = g_strdup("3.0"); else if (VCARD_VERSION_4_0 == version) vv = g_strdup("4.0"); return vv; } VCardParserResponse vcard_parse_text(const gchar* text, VCardVersion* version, GHashTable** vcard) { VCardParserResponse r = VCARD_PARSER_ERROR; if (text == NULL || strlen(text) < 1) return VCARD_PARSER_OK; gchar** b = g_strsplit(text, "\n", 0); gchar** buf = vcard_text_normalize((const gchar**)b); g_strfreev(b); guint l = g_strv_length(buf); //begin, end and version line is required r = VCARD_PARSER_BAD_FORMAT; if (buf && l > 3) { if (g_strcmp0(buf[0], "BEGIN:VCARD") == 0 && g_strcmp0(buf[l-1], "END:VCARD") == 0) { r = has_required_version((const gchar **)buf, version); if (r == VCARD_PARSER_OK) { init_g_hash_table(vcard, *version); if (vcard && *vcard) { r = vcard_parse(buf, *version, vcard); } else { r = VCARD_PARSER_ERROR; } } } } g_strfreev(buf); return r; }