1 /*
2 
3   $Id$
4 
5   G N O K I I
6 
7   A Linux/Unix toolset and driver for the mobile phones.
8 
9   This file is part of gnokii.
10 
11   Gnokii is free software; you can redistribute it and/or modify
12   it under the terms of the GNU General Public License as published by
13   the Free Software Foundation; either version 2 of the License, or
14   (at your option) any later version.
15 
16   Gnokii is distributed in the hope that it will be useful,
17   but WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   GNU General Public License for more details.
20 
21   You should have received a copy of the GNU General Public License
22   along with gnokii; if not, write to the Free Software
23   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24 
25   Copyright (C) 2002 Pavel Machek <pavel@ucw.cz>
26   Copyright (C) 2003-2004 Pawel Kot
27 
28  */
29 
30 #include "config.h"
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "compat.h"
37 #include "gnokii.h"
38 #include "gnokii-internal.h"
39 
40 typedef struct {
41 	char *str;
42 	char *end;
43 	unsigned int len;
44 } vcard_string;
45 
46 /*
47   Copies at most @maxcount strings, each of @maxsize length to the given
48   pointers from a vCard property, such as
49   ADR;TYPE=HOME,PREF:PO Box;Ext Add;Street;Town;State;ZIP;Country
50 
51   Returns: the number of strings copied
52  */
copy_fields(const char * str,int maxcount,size_t maxsize,...)53 int copy_fields(const char *str, int maxcount, size_t maxsize, ...)
54 {
55 	va_list ap;
56 	int count;
57 	size_t size;
58 	char *dest;
59 
60 	va_start(ap, maxsize);
61 
62 	for (count = maxcount; count && *str; count--) {
63 		dest = va_arg(ap, char *);
64 
65 		size = maxsize;
66 		while (size && *str) {
67 			if (*str == ';') {
68 				str++;
69 				break;
70 			}
71 			*dest++ = *str++;
72 			size--;
73 		}
74 		*dest = '\0';
75 	}
76 
77 	va_end(ap);
78 
79 	return maxcount - count;
80 }
81 
82 /* Write a string to a file doing folding when needed (see RFC 2425) */
vcard_append_printf(vcard_string * str,const char * fmt,...)83 static void vcard_append_printf(vcard_string *str, const char *fmt, ...)
84 {
85 	char buf[1024];
86 	va_list ap;
87 	size_t len, to_copy;
88 	int lines, l;
89 	char *s = "\r\n";
90 
91 	va_start(ap, fmt);
92 	vsnprintf(buf, sizeof(buf), fmt, ap);
93 	va_end(ap);
94 
95 	/* Split lines according to sections 5.8.1 and 5.8.2 of http://www.ietf.org/rfc/rfc2425.txt */
96 	len = strlen(buf);
97 	/* Number of lines needed after the first one:
98 	 * at most 75 characters in the first line
99 	 * at most space+74 characters in each line beyond the first */
100 	if (len < 2)
101 		lines = 0;
102 	else
103 		lines = (len - 75 + 73) / 74;
104 	/* Current string length
105 	 * plus length of the string to be appended
106 	 * plus 2 characters for \r\n at the end of the first line,
107 	 * plus 3 characters for space at the beginning and \r\n at the end of each line beyond the first
108 	 * and a NUL byte to finish it off */
109 	str->str = realloc(str->str, str->len + len + 2 + lines * 3 + 1);
110 	str->end = str->str + str->len;
111 
112 	to_copy = GNOKII_MIN(75, len);
113 	memcpy(str->end, buf, to_copy);
114 	str->end += to_copy;
115 	len -= to_copy;
116 
117 	for (l = 0; l < lines; l++) {
118 		char *s = "\r\n ";
119 
120 		memcpy(str->end, s, 3);
121 		str->end += 3;
122 		to_copy = GNOKII_MIN(74, len);
123 		memcpy(str->end, buf + 75 + 74 * l, to_copy);
124 		str->end += to_copy;
125 		len -= to_copy;
126 	}
127 
128 	memcpy(str->end, s, 2);
129 	str->end += 2;
130 	str->end[0] = '\0';
131 
132 	str->len = str->end - str->str;
133 }
134 
gn_phonebook2vcardstr(gn_phonebook_entry * entry)135 GNOKII_API char * gn_phonebook2vcardstr(gn_phonebook_entry *entry)
136 {
137 	vcard_string str;
138 	int i;
139 	char name[2 * GN_PHONEBOOK_NAME_MAX_LENGTH];
140 
141 	memset(&str, 0, sizeof(str));
142 
143 	vcard_append_printf(&str, "BEGIN:VCARD");
144 	vcard_append_printf(&str, "VERSION:3.0");
145 	add_slashes(name, entry->name, sizeof(name), strlen(entry->name));
146 	vcard_append_printf(&str, "FN:%s", name);
147 
148 	if (entry->person.has_person)
149 		vcard_append_printf(&str, "N:%s;%s;%s;%s;%s",
150 			entry->person.family_name[0]        ? entry->person.family_name        : "",
151 			entry->person.given_name[0]         ? entry->person.given_name         : "",
152 			entry->person.additional_names[0]   ? entry->person.additional_names   : "",
153 			entry->person.honorific_prefixes[0] ? entry->person.honorific_prefixes : "",
154 			entry->person.honorific_suffixes[0] ? entry->person.honorific_suffixes : "");
155 	else
156 		vcard_append_printf(&str, "N:%s", name);
157 
158 	if (*entry->number)
159 		vcard_append_printf(&str, "TEL;TYPE=PREF,VOICE:%s", entry->number);
160 	vcard_append_printf(&str, "X-GSM-MEMORY:%s", gn_memory_type2str(entry->memory_type));
161 	vcard_append_printf(&str, "X-GSM-LOCATION:%d", entry->location);
162 	vcard_append_printf(&str, "X-GSM-CALLERGROUP:%d", entry->caller_group);
163 	vcard_append_printf(&str, "CATEGORIES:%s", gn_phonebook_group_type2str(entry->caller_group));
164 
165 	if (entry->address.has_address)
166 		vcard_append_printf(&str, "ADR;TYPE=HOME,PREF:%s;%s;%s;%s;%s;%s;%s",
167 			entry->address.post_office_box[0]  ? entry->address.post_office_box  : "",
168 			entry->address.extended_address[0] ? entry->address.extended_address : "",
169 			entry->address.street[0]           ? entry->address.street           : "",
170 			entry->address.city[0]             ? entry->address.city             : "",
171 			entry->address.state_province[0]   ? entry->address.state_province   : "",
172 			entry->address.zipcode[0]          ? entry->address.zipcode          : "",
173 			entry->address.country[0]          ? entry->address.country          : "");
174 
175 	/* Add ext. pbk info if required */
176 	for (i = 0; i < entry->subentries_count; i++) {
177 		switch (entry->subentries[i].entry_type) {
178 		case GN_PHONEBOOK_ENTRY_Email:
179 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
180 			vcard_append_printf(&str, "EMAIL;TYPE=INTERNET:%s", name);
181 			break;
182 		case GN_PHONEBOOK_ENTRY_Postal:
183 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
184 			vcard_append_printf(&str, "ADR;TYPE=HOME:%s", name);
185 			break;
186 		case GN_PHONEBOOK_ENTRY_Note:
187 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
188 			vcard_append_printf(&str, "NOTE:%s", name);
189 			break;
190 		case GN_PHONEBOOK_ENTRY_Number:
191 			switch (entry->subentries[i].number_type) {
192 			case GN_PHONEBOOK_NUMBER_Home:
193 				vcard_append_printf(&str, "TEL;TYPE=HOME:%s", entry->subentries[i].data.number);
194 				break;
195 			case GN_PHONEBOOK_NUMBER_Mobile:
196 				vcard_append_printf(&str, "TEL;TYPE=CELL:%s", entry->subentries[i].data.number);
197 				break;
198 			case GN_PHONEBOOK_NUMBER_Fax:
199 				vcard_append_printf(&str, "TEL;TYPE=FAX:%s", entry->subentries[i].data.number);
200 				break;
201 			case GN_PHONEBOOK_NUMBER_Work:
202 				vcard_append_printf(&str, "TEL;TYPE=WORK:%s", entry->subentries[i].data.number);
203 				break;
204 			case GN_PHONEBOOK_NUMBER_None:
205 			case GN_PHONEBOOK_NUMBER_Common:
206 				vcard_append_printf(&str, "TEL;TYPE=VOICE:%s", entry->subentries[i].data.number);
207 				break;
208 			case GN_PHONEBOOK_NUMBER_General:
209 				vcard_append_printf(&str, "TEL;TYPE=PREF:%s", entry->subentries[i].data.number);
210 				break;
211 			default:
212 				vcard_append_printf(&str, "TEL;TYPE=X-UNKNOWN-%d: %s", entry->subentries[i].number_type, entry->subentries[i].data.number);
213 				break;
214 			}
215 			break;
216 		case GN_PHONEBOOK_ENTRY_URL:
217 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
218 			vcard_append_printf(&str, "URL:%s", name);
219 			break;
220 		case GN_PHONEBOOK_ENTRY_JobTitle:
221 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
222 			vcard_append_printf(&str, "TITLE:%s", name);
223 			break;
224 		case GN_PHONEBOOK_ENTRY_Company:
225 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
226 			vcard_append_printf(&str, "ORG:%s", name);
227 			break;
228 		case GN_PHONEBOOK_ENTRY_Nickname:
229 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
230 			vcard_append_printf(&str, "NICKNAME:%s", name);
231 			break;
232 		case GN_PHONEBOOK_ENTRY_Birthday:
233 			vcard_append_printf(&str, "BDAY:%04u%02u%02uT%02u%02u%02u",
234 				entry->subentries[i].data.date.year, entry->subentries[i].data.date.month, entry->subentries[i].data.date.day,
235 				entry->subentries[i].data.date.hour, entry->subentries[i].data.date.minute, entry->subentries[i].data.date.second);
236 			break;
237 		case GN_PHONEBOOK_ENTRY_Date:
238 			/* This is used when reading DC, MC, RC so the vcard *reader* won't read it back */
239 			vcard_append_printf(&str, "REV:%04u%02u%02uT%02u%02u%02u",
240 				entry->subentries[i].data.date.year, entry->subentries[i].data.date.month, entry->subentries[i].data.date.day,
241 				entry->subentries[i].data.date.hour, entry->subentries[i].data.date.minute, entry->subentries[i].data.date.second);
242 			break;
243 		case GN_PHONEBOOK_ENTRY_ExtGroup:
244 			vcard_append_printf(&str, "X-GSM-CALLERGROUPID:%d", entry->subentries[i].data.id);
245 			break;
246 		case GN_PHONEBOOK_ENTRY_PTTAddress:
247 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
248 			vcard_append_printf(&str, "X-SIP;POC:%s", name);
249 			break;
250 		case GN_PHONEBOOK_ENTRY_UserID:
251 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
252 			vcard_append_printf(&str, "X-WV-ID:%s", name);
253 			break;
254 		case GN_PHONEBOOK_ENTRY_FirstName:
255 		case GN_PHONEBOOK_ENTRY_Group:
256 		case GN_PHONEBOOK_ENTRY_Image:
257 		case GN_PHONEBOOK_ENTRY_LastName:
258 		case GN_PHONEBOOK_ENTRY_Location:
259 		case GN_PHONEBOOK_ENTRY_Logo:
260 		case GN_PHONEBOOK_ENTRY_LogoSwitch:
261 		case GN_PHONEBOOK_ENTRY_Pointer:
262 		case GN_PHONEBOOK_ENTRY_Ringtone:
263 		case GN_PHONEBOOK_ENTRY_RingtoneAdv:
264 		case GN_PHONEBOOK_ENTRY_Video:
265 			/* Ignore */
266 			break;
267 		case GN_PHONEBOOK_ENTRY_City:
268 		case GN_PHONEBOOK_ENTRY_Country:
269 		case GN_PHONEBOOK_ENTRY_ExtendedAddress:
270 		case GN_PHONEBOOK_ENTRY_FormalName:
271 		case GN_PHONEBOOK_ENTRY_Name:
272 		case GN_PHONEBOOK_ENTRY_PostalAddress:
273 		case GN_PHONEBOOK_ENTRY_StateProvince:
274 		case GN_PHONEBOOK_ENTRY_Street:
275 		case GN_PHONEBOOK_ENTRY_ZipCode:
276 			add_slashes(name, entry->subentries[i].data.number, sizeof(name), strlen(entry->subentries[i].data.number));
277 			vcard_append_printf(&str, "X-GNOKII-%d:%s", entry->subentries[i].entry_type, name);
278 			break;
279 		}
280 	}
281 
282 	vcard_append_printf(&str, "END:VCARD");
283 	/* output an empty line */
284 	vcard_append_printf(&str, "");
285 
286 	return str.str;
287 }
288 
gn_phonebook2vcard(FILE * f,gn_phonebook_entry * entry,char * location)289 GNOKII_API int gn_phonebook2vcard(FILE *f, gn_phonebook_entry *entry, char *location)
290 {
291 	char *vcard;
292 	int retval;
293 
294 	vcard = gn_phonebook2vcardstr(entry);
295 	if (vcard == NULL)
296 		return -1;
297 	retval = fputs(vcard, f);
298 	free(vcard);
299 
300 	return retval;
301 }
302 
303 #define BEGINS(a) ( !strncmp(buf, a, strlen(a)) )
304 #define STRIP(a, b) strip_slashes(b, buf+strlen(a), line_len - strlen(a), GN_PHONEBOOK_NAME_MAX_LENGTH)
305 #define STORE3(a, b) if (BEGINS(a)) { STRIP(a, b); }
306 #define STORE2(a, b, c) if (BEGINS(a)) { c; STRIP(a, b); continue; }
307 #define STORE(a, b) STORE2(a, b, (void) 0)
308 #define STOREINT(a, b) if (BEGINS(a)) { b = atoi(buf+strlen(a)); continue; }
309 
310 #define STORESUB(a, c) if (entry->subentries_count == GN_PHONEBOOK_SUBENTRIES_MAX_NUMBER) return -1; \
311 				STORE2(a, entry->subentries[entry->subentries_count++].data.number, \
312 				entry->subentries[entry->subentries_count].entry_type = c);
313 #define STORENUM(a, c) if (entry->subentries_count == GN_PHONEBOOK_SUBENTRIES_MAX_NUMBER) return -1; \
314 				STORE2(a, entry->subentries[entry->subentries_count++].data.number, \
315 				entry->subentries[entry->subentries_count].entry_type = GN_PHONEBOOK_ENTRY_Number; \
316 				entry->subentries[entry->subentries_count].number_type = c);
317 #define STOREMEMTYPE(a, memory_type) if (BEGINS(a)) { STRIP(a, memory_name); memory_type = gn_str2memory_type(memory_name); }
318 #define STOREDATE(a, c) if (BEGINS(a)) { \
319 				if (entry->subentries_count == GN_PHONEBOOK_SUBENTRIES_MAX_NUMBER) return -1; \
320 				memset(&entry->subentries[entry->subentries_count].data.date, 0, sizeof(entry->subentries[entry->subentries_count].data.date)); \
321 				sscanf(buf+strlen(a), "%4u%2u%2uT%2u%2u%2u", \
322 					&entry->subentries[entry->subentries_count].data.date.year, \
323 					&entry->subentries[entry->subentries_count].data.date.month, \
324 					&entry->subentries[entry->subentries_count].data.date.day, \
325 					&entry->subentries[entry->subentries_count].data.date.hour, \
326 					&entry->subentries[entry->subentries_count].data.date.minute, \
327 					&entry->subentries[entry->subentries_count].data.date.second); \
328 				entry->subentries[entry->subentries_count].entry_type = c; \
329 				entry->subentries_count++; \
330 			} while (0)
331 
332 #undef ERROR
333 #define ERROR(a) fprintf(stderr, "%s\n", a)
334 
str_append_printf(vcard_string * str,const char * s)335 static void str_append_printf(vcard_string *str, const char *s)
336 {
337 	int len;
338 
339 	if (str->str == NULL) {
340 		str->str = strdup(s);
341 		str->len = strlen(s) + 1;
342 		return;
343 	}
344 
345 	len = strlen(s);
346 	str->str = realloc(str->str, len + str->len);
347 
348 	memcpy(str->str + str->len - 1, s, len);
349 	str->len = str->len + len;
350 	str->end = str->str + str->len;
351 
352 	/* NULL-terminate the string now */
353 	*(str->end - 1) = '\0';
354 }
355 
gn_vcard2phonebook(FILE * f,gn_phonebook_entry * entry)356 GNOKII_API int gn_vcard2phonebook(FILE *f, gn_phonebook_entry *entry)
357 {
358 	char buf[1024];
359 	vcard_string str;
360 	int retval;
361 
362 	memset(&str, 0, sizeof(str));
363 
364 	while (1) {
365 		if (!fgets(buf, 1024, f))
366 			return -1;
367 		if (BEGINS("BEGIN:VCARD"))
368 			break;
369 	}
370 
371 	str_append_printf(&str, "BEGIN:VCARD\r\n");
372 	while (fgets(buf, 1024, f)) {
373 		str_append_printf(&str, buf);
374 		if (BEGINS("END:VCARD"))
375 			break;
376 	}
377 
378 	retval = gn_vcardstr2phonebook(str.str, entry);
379 	free(str.str);
380 
381 	return retval;
382 }
383 
384 /* We assume gn_phonebook_entry is ready for writing in, ie. no rubbish inside */
gn_vcardstr2phonebook(const char * vcard,gn_phonebook_entry * entry)385 GNOKII_API int gn_vcardstr2phonebook(const char *vcard, gn_phonebook_entry *entry)
386 {
387 	char memloc[10];
388 	char memory_name[10];
389 	char *v, *fold, *s, **lines;
390 	int num_lines, i;
391 
392 	if (vcard == NULL)
393 		return -1;
394 
395 	memset(memloc, 0, sizeof(memloc));
396 	memset(memory_name, 0, sizeof(memory_name));
397 
398 	/* Remove folding */
399 	v = strdup(vcard);
400 	fold = strstr(v, "\r\n");
401 	while (fold != NULL) {
402 		memmove(fold, fold + 1, strlen(fold));
403 		fold = strstr(fold, "\r\n");
404 	}
405 	fold = strstr(v, "\n ");
406 	while (fold != NULL) {
407 		memmove(fold, fold + 2, strlen(fold) - 1);
408 		fold = strstr(fold, "\n ");
409 	}
410 	fold = strstr(v, "\n\t");
411 	while (fold != NULL) {
412 		memmove(fold, fold + 2, strlen(fold) - 1);
413 		fold = strstr(fold, "\n\t");
414 	}
415 
416 	/* Count the number of lines */
417 	s = strstr(v, "\n");
418 	for (num_lines = 0; s != NULL; num_lines++) {
419 		s = strstr(s + 1, "\n");
420 	}
421 
422 	/* FIXME on error STORESUB() and STORENUM() leak memory allocated by gnokii_strsplit() */
423 	lines = gnokii_strsplit(v, "\n", num_lines);
424 
425 	for (i = 0; i < num_lines; i++) {
426 		char *buf;
427 		int line_len;
428 
429 		if (lines[i] == NULL || *lines[i] == '\0')
430 			continue;
431 
432 		buf = lines[i];
433 		line_len = strlen(buf);
434 
435 		/* Strip traling '\r's */
436 		while (line_len > 0 && buf[line_len-1] == '\r')
437 			buf[--line_len] = '\0';
438 
439 		if (BEGINS("N:")) {
440 			if (0 < copy_fields(buf +2 , 5, GN_PHONEBOOK_PERSON_MAX_LENGTH,
441 				entry->person.family_name,
442 				entry->person.given_name,
443 				entry->person.additional_names,
444 				entry->person.honorific_prefixes,
445 				entry->person.honorific_suffixes
446 			)) {
447 				entry->person.has_person = 1;
448 				/* Temporary solution. In the phone driver we should handle person struct */
449 				if (entry->person.family_name[0]) {
450 					strcpy(entry->subentries[entry->subentries_count].data.number, entry->person.family_name);
451 					entry->subentries[entry->subentries_count].entry_type = GN_PHONEBOOK_ENTRY_LastName;
452 					entry->subentries_count++;
453 				}
454 				if (entry->person.given_name[0]) {
455 					strcpy(entry->subentries[entry->subentries_count].data.number, entry->person.given_name);
456 					entry->subentries[entry->subentries_count].entry_type = GN_PHONEBOOK_ENTRY_FirstName;
457 					entry->subentries_count++;
458 				}
459 			}
460 			continue;
461 		}
462 		STORE("FN:", entry->name);
463 		STORE("TEL;TYPE=PREF,VOICE:", entry->number);
464 
465 		if (BEGINS("ADR;TYPE=HOME,PREF:")) {
466 			if (0 < copy_fields(buf + 19, 7, GN_PHONEBOOK_ADDRESS_MAX_LENGTH,
467 				entry->address.post_office_box,
468 				entry->address.extended_address,
469 				entry->address.street,
470 				entry->address.city,
471 				entry->address.state_province,
472 				entry->address.zipcode,
473 				entry->address.country
474 			)) {
475 				entry->address.has_address = 1;
476 			}
477 			continue;
478 		}
479 
480 		STORESUB("URL:", GN_PHONEBOOK_ENTRY_URL);
481 		STORESUB("EMAIL;TYPE=INTERNET:", GN_PHONEBOOK_ENTRY_Email);
482 		STORESUB("ADR;TYPE=HOME:", GN_PHONEBOOK_ENTRY_Postal);
483 		STORESUB("NOTE:", GN_PHONEBOOK_ENTRY_Note);
484 
485 		STORESUB("TITLE:", GN_PHONEBOOK_ENTRY_JobTitle);
486 		STORESUB("ORG:", GN_PHONEBOOK_ENTRY_Company);
487 		STORESUB("NICKNAME:", GN_PHONEBOOK_ENTRY_Nickname);
488 		STORESUB("X-SIP;POC:", GN_PHONEBOOK_ENTRY_PTTAddress);
489 		STORESUB("X-WV-ID:", GN_PHONEBOOK_ENTRY_UserID);
490 
491 		STOREDATE("BDAY:", GN_PHONEBOOK_ENTRY_Birthday);
492 
493 #if 1
494 		/* libgnokii 0.6.25 deprecates X_GSM_STORE_AT in favour of X-GSM-MEMORY and X-GSM-LOCATION and X_GSM_CALLERGROUP in favour of X-GSM-CALLERGROUP */
495 		STORE3("X_GSM_STORE_AT:", memloc);
496 		/* if the field is present and correctly formed */
497 		if (strlen(memloc) > 2) {
498 			entry->location = atoi(memloc + 2);
499 			memloc[2] = 0;
500 			entry->memory_type = gn_str2memory_type(memloc);
501 			continue;
502 		}
503 		STOREINT("X_GSM_CALLERGROUP:", entry->caller_group);
504 #endif
505 		STOREMEMTYPE("X-GSM-MEMORY:", entry->memory_type);
506 		STOREINT("X-GSM-LOCATION:", entry->location);
507 		STOREINT("X-GSM-CALLERGROUP:", entry->caller_group);
508 
509 		STORENUM("TEL;TYPE=HOME:", GN_PHONEBOOK_NUMBER_Home);
510 		STORENUM("TEL;TYPE=CELL:", GN_PHONEBOOK_NUMBER_Mobile);
511 		STORENUM("TEL;TYPE=FAX:", GN_PHONEBOOK_NUMBER_Fax);
512 		STORENUM("TEL;TYPE=WORK:", GN_PHONEBOOK_NUMBER_Work);
513 		STORENUM("TEL;TYPE=PREF:", GN_PHONEBOOK_NUMBER_General);
514 		STORENUM("TEL;TYPE=VOICE:", GN_PHONEBOOK_NUMBER_Common);
515 		if (BEGINS("X-GSM-CALLERGROUPID:")) {
516 			entry->subentries[entry->subentries_count].data.id = atoi(buf + strlen("X-GSM-CALLERGROUPID:"));
517 			entry->subentries[entry->subentries_count].entry_type = GN_PHONEBOOK_ENTRY_ExtGroup;
518 			entry->subentries_count++;
519         		continue;
520 		}
521 
522 		if (BEGINS("END:VCARD"))
523 			break;
524 	}
525 	free(v);
526 	gnokii_strfreev(lines);
527 
528 	return 0;
529 }
530 
531