--- /dev/null
+/*
+ * vcard-parser.c
+ *
+ * Copyright 2019 Michael Rasmussen <mir@datanom.net>
+ *
+ * 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 <vcard-parser.h>
+
+#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;
+}