]> git.datanom.net - vcard-parser.git/blob - src/vcard-parser.c
New function
[vcard-parser.git] / src / vcard-parser.c
1 /*
2 * vcard-parser.c
3 *
4 * Copyright 2019 Michael Rasmussen <mir@datanom.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #include <vcard-parser.h>
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 static gchar* Properties[VCARD_PROPERTIES+1] = {
29 "N",
30 "FN",
31 "PHOTO",
32 "BDAY",
33 "ADR",
34 "LABEL",
35 "TEL",
36 "EMAIL",
37 "MAILER",
38 "GEO",
39 "TITLE",
40 "ROLE",
41 "LOGO",
42 "ORG",
43 "NOTE",
44 "REV",
45 "SOUND",
46 "URL",
47 "UID",
48 "VERSION",
49 "KEY",
50 "TZ",
51 "SOURCE",
52 "AGENT",
53 "PROFILE",
54 "CATEGORIES",
55 "SORT-STRING",
56 "PRODID",
57 "NICKNAME",
58 "NAME",
59 "CLASS",
60 "FBURL",
61 "CAPURI",
62 "CALURI",
63 "CALADRURI",
64 "IMPP",
65 "XML",
66 "ANNIVERSARY",
67 "CLIENTPIDMAP",
68 "LANG",
69 "GENDER",
70 "KIND",
71 "MEMBER",
72 "RELATED",
73 "BIRTHPLACE",
74 "DEATHPLACE",
75 "DEATHDATE",
76 "EXPERTISE",
77 "HOBBY",
78 "INTEREST",
79 "ORG-DIRECTORY",
80 NULL
81 };
82
83 static void vcard_property_free(gpointer data) {
84 if (!data) return;
85 VCardProperty* vp = (VCardProperty*) data;
86 g_free(vp->name);
87 g_free(vp->value);
88 g_free(vp);
89 }
90
91 static Property get_property(const gchar* property) {
92 for (Property i = N; i < VCARD_PROPERTIES; i++) {
93 if (g_strcmp0(Properties[i], property) == 0) return i;
94 }
95
96 return VCARD_PROPERTIES;
97 }
98
99 static gboolean check_support(Property property, VCardVersion version) {
100 gboolean support = FALSE;
101
102 if (version == VCARD_VERSION_2_1) {
103 support = property <= TZ ? TRUE : FALSE;
104 } else if (version == VCARD_VERSION_3_0) {
105 support = property <= IMPP ? TRUE : FALSE;
106 } else if (version == VCARD_VERSION_4_0) {
107 support = property <= VCARD_PROPERTIES ? TRUE : FALSE;
108 }
109
110 return support;
111 }
112
113 static VCardParserResponse has_required_version(const gchar** buf, VCardVersion* version) {
114 const gchar** tmp = buf;
115 gboolean found = FALSE;
116 VCardParserResponse result = VCARD_PARSER_VERSION_MISSING;
117
118 while (*tmp && !found) {
119 if (!g_str_has_prefix(*tmp, "VERSION")) {
120 tmp++;
121 } else {
122 found = TRUE;
123 gchar** b = g_strsplit(*tmp, ":", 0);
124 if (g_strv_length(b) == 2) {
125 if (*version == VCARD_VERSION_DETECT) {
126 *version = str_2_vcard_version(b[1]);
127 result = VCARD_PARSER_OK;
128 } else {
129 if (*version == str_2_vcard_version(b[1])) {
130 result = VCARD_PARSER_OK;
131 } else
132 result = VCARD_PARSER_VERSION_MISMATCH;
133 }
134 }
135 g_strfreev(b);
136 }
137 }
138
139 return result;
140 }
141
142 static gchar** vcard_text_normalize(const gchar** text) {
143 gchar** new = NULL;
144 const gchar** tmp = text;
145 int count = 0;
146 gchar* str = NULL;
147
148 if (text == NULL) return NULL;
149
150 new = g_new0(gchar*, g_strv_length((gchar **) text) + 1);
151 while (*tmp && g_strcmp0(*tmp, "") != 0) {
152 if (*tmp[0] == ' ') {
153 // Multi line value
154 if (str) {
155 gchar* s = g_strdup(str);
156 g_free(str);
157 str = g_strconcat(s, "\n", *tmp+1, NULL);
158 } else
159 str = g_strdup(*tmp+1);
160 } else {
161 if (str) {
162 new[count++] = g_strdup(str);
163 g_free(str);
164 str = NULL;
165 }
166 str = g_strdup(*tmp);
167 }
168 tmp++;
169 }
170 if (str) {
171 new[count++] = g_strdup(str);
172 g_free(str);
173 }
174
175 return new;
176 }
177
178 static VCardParserResponse vcard_parse(gchar** text, VCardVersion version, GHashTable** vcard) {
179 VCardParserResponse r = VCARD_PARSER_ERROR;
180 guint l = g_strv_length(text);
181 gboolean support;
182
183 for (guint i = 0; i < l; i++) {
184 gchar** b = g_strsplit(text[i], ":", 2);
185 if (g_strv_length(b) != 2 || !b[0] || !b[1]) {
186 destroy_hash_table(*vcard);
187 *vcard = NULL;
188 g_strfreev(b);
189 return VCARD_PARSER_ERROR;
190 }
191 gchar** c = g_strsplit(b[0], ";", 0);
192 if (g_strcmp0(c[0], "BEGIN") == 0 ||
193 g_strcmp0(c[0], "VERSION") == 0 ||
194 g_strcmp0(c[0], "END") == 0) {
195 g_strfreev(c);
196 g_strfreev(b);
197 continue;
198 }
199 if (g_str_has_prefix(c[0], "X-")) {
200 support = TRUE;
201 } else {
202 Property property = get_property(c[0]);
203 support = check_support(property, version);
204 }
205 if (support) {
206 GSList* value = g_hash_table_lookup(*vcard, c[0]);
207 VCardProperty* vp = g_new(VCardProperty, 1);
208 vp->name = g_strdup(b[0]);
209 vp->value = g_strdup(b[1]);
210 value = g_slist_append(value, vp);
211 g_hash_table_insert(*vcard, g_strdup(c[0]), value);
212 } else {
213 destroy_hash_table(*vcard);
214 *vcard = NULL;
215 g_strfreev(c);
216 g_strfreev(b);
217 return VCARD_PARSER_ATTRIBUTE_VERSION_MISMATCH;
218 }
219 g_strfreev(c);
220 g_strfreev(b);
221 }
222 r = VCARD_PARSER_OK;
223
224 return r;
225 }
226
227 Property vcard_max_property(VCardVersion version) {
228 Property p = VCARD_PROPERTIES;
229
230 if (version == VCARD_VERSION_2_1) {
231 p = TZ;
232 } else if (version == VCARD_VERSION_3_0) {
233 p = IMPP;
234 } else if (version == VCARD_VERSION_4_0) {
235 p = VCARD_PROPERTIES - 1;
236 }
237
238 return p;
239 }
240
241 void destroy_hash_table(GHashTable* ht) {
242 GHashTableIter iter;
243 gpointer key, value;
244
245 g_hash_table_iter_init (&iter, ht);
246 while (g_hash_table_iter_next(&iter, &key, &value)) {
247 if (value)
248 g_slist_free_full((GSList *) value, vcard_property_free);
249 }
250
251 g_hash_table_destroy(ht);
252 }
253
254 void init_g_hash_table(GHashTable** ht, VCardVersion version) {
255 if (!ht) return;
256
257 Property property = vcard_max_property(version);
258 if (property < VCARD_PROPERTIES) {
259 *ht = g_hash_table_new_full(
260 g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
261 for (Property i = N; i <= property; i++) {
262 g_hash_table_insert(*ht, g_strdup(Properties[i]), NULL);
263 }
264 }
265 }
266
267 VCardVersion str_2_vcard_version(const gchar* version) {
268 VCardVersion vv = VCARD_VERSION_DETECT;
269
270 if (version) {
271 if (g_strcmp0("2.1", version) == 0)
272 vv = VCARD_VERSION_2_1;
273 else if (g_strcmp0("3.0", version) == 0)
274 vv = VCARD_VERSION_3_0;
275 else if (g_strcmp0("4.0", version) == 0)
276 vv = VCARD_VERSION_4_0;
277 else
278 vv = VCARD_VERSION_UNSUPPORTED;
279 }
280
281 return vv;
282 }
283
284 gchar* vcard_version_2_str(VCardVersion version) {
285 gchar* vv = NULL;
286
287 if (VCARD_VERSION_2_1 == version)
288 vv = g_strdup("2.1");
289 else if (VCARD_VERSION_3_0 == version)
290 vv = g_strdup("3.0");
291 else if (VCARD_VERSION_4_0 == version)
292 vv = g_strdup("4.0");
293
294 return vv;
295 }
296
297 VCardParserResponse vcard_parse_text(const gchar* text, VCardVersion* version, GHashTable** vcard) {
298 VCardParserResponse r = VCARD_PARSER_ERROR;
299
300 if (text == NULL || strlen(text) < 1) return VCARD_PARSER_OK;
301
302 gchar** b = g_strsplit(text, "\n", 0);
303 gchar** buf = vcard_text_normalize((const gchar**)b);
304 g_strfreev(b);
305 guint l = g_strv_length(buf);
306 //begin, end and version line is required
307 r = VCARD_PARSER_BAD_FORMAT;
308 if (buf && l > 3) {
309 if (g_strcmp0(buf[0], "BEGIN:VCARD") == 0 && g_strcmp0(buf[l-1], "END:VCARD") == 0) {
310 r = has_required_version((const gchar **)buf, version);
311 if (r == VCARD_PARSER_OK) {
312 init_g_hash_table(vcard, *version);
313 if (vcard && *vcard) {
314 r = vcard_parse(buf, *version, vcard);
315 } else {
316 r = VCARD_PARSER_ERROR;
317 }
318 }
319 }
320 }
321 g_strfreev(buf);
322
323 return r;
324 }
This page took 0.100375 seconds and 6 git commands to generate.