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