From: Michael Rasmussen Date: Mon, 30 Dec 2019 21:55:59 +0000 (+0100) Subject: Complete version 0.1 X-Git-Url: http://git.datanom.net/vcard-parser.git/commitdiff_plain/949e2bcc13b4f440484ff4ae110c47a874226323 Complete version 0.1 Signed-off-by: Michael Rasmussen --- diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 0000000..b500d8c --- /dev/null +++ b/src/globals.h @@ -0,0 +1,41 @@ +/* + * globals.h + * + * 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. + */ + +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +#include + +G_BEGIN_DECLS + +typedef enum { + VCARD_VERSION_UNSUPPORTED, + VCARD_VERSION_DETECT, + VCARD_VERSION_2_1, + VCARD_VERSION_3_0, + VCARD_VERSION_4_0, +} VCardVersion; + +typedef struct _VCard VCard; + +G_END_DECLS + +#endif diff --git a/src/vcard-parser.c b/src/vcard-parser.c new file mode 100644 index 0000000..056adbd --- /dev/null +++ b/src/vcard-parser.c @@ -0,0 +1,335 @@ +/* + * 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) { +/* Property p = VCARD_PROPERTIES; + gchar* lookup = NULL; + + if (g_strcmp0("ORG-DIRECTORY", property) == 0) + lookup = g_strdup("ORG_DIRECTORY"); + else if (g_strcmp0("SORT-STRING", property) == 0) + lookup = g_strdup("SORT_STRING"); + else + lookup = g_strdup(property); + + for (int i = 0; i < VCARD_PROPERTIES; i++) { + if (g_strcmp0(Properties[i], lookup) == 0) { + p = i; + break; + } + } + g_free(lookup);*/ + 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; +} + +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); +} + +static 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); + } + } +} + +static VCardParserResponse vcard_parse(gchar** text, VCardVersion version, GHashTable** vcard) { + VCardParserResponse r = VCARD_PARSER_ERROR; + guint l = g_strv_length(text); + + 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; + } + Property property = get_property(c[0]); + if (check_support(property, version)) { + 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; +} + +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; +}