Work on vcards
[citadel.git] / webcit / vcard_edit.c
1 /*
2  * Copyright (c) 1996-2012 by the citadel.org team
3  *
4  * This program is open source software.  You can redistribute it and/or
5  * modify it under the terms of the GNU General Public License, version 3.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  */
12
13 #include "webcit.h"
14 #include "webserver.h"
15 #include "calendar.h"
16
17 CtxType CTX_VCARD = CTX_NONE;
18 long VCEnumCounter = 0;
19
20 typedef enum _VCStrEnum {
21         FlatString,
22         StringCluster,
23         PhoneNumber,
24         EmailAddr,
25         Street,
26         Number,
27         AliasFor,
28         Base64BinaryAttachment,
29         TerminateList
30 }VCStrEnum;
31 typedef struct vcField vcField;
32 struct vcField {
33         ConstStr STR;
34         VCStrEnum Type;
35         vcField *Sub;
36         long cval;
37         const char *Str;
38 };
39
40 vcField VCStr_Ns [] = {
41         {{HKEY("last")},   FlatString,    NULL, 0, "Last Name"},
42         {{HKEY("first")},  FlatString,    NULL, 0, "First Name"},
43         {{HKEY("middle")}, FlatString,    NULL, 0, "Middle Name"},
44         {{HKEY("prefix")}, FlatString,    NULL, 0, "Prefix"},
45         {{HKEY("suffix")}, FlatString,    NULL, 0, "Suffix"},
46         {{HKEY("")},       TerminateList, NULL, 0, ""}
47 };
48
49 vcField VCStr_Addrs [] = {
50         {{HKEY("POBox")},    FlatString,    NULL, 0, "PO box"},
51         {{HKEY("address")},  FlatString,    NULL, 0, "Address"},
52         {{HKEY("address2")}, FlatString,    NULL, 0, ""},
53         {{HKEY("city")},     FlatString,    NULL, 0, "City"},
54         {{HKEY("state")},    FlatString,    NULL, 0, "State"},
55         {{HKEY("zip")},      FlatString,    NULL, 0, "ZIP code"},
56         {{HKEY("country")},  FlatString,    NULL, 0, "Country"},
57         {{HKEY("")},         TerminateList, NULL, 0, ""}
58 };
59
60 vcField VCStrE [] = {
61         {{HKEY("version")},         Number,                 NULL,        0, ""},
62         {{HKEY("rev")},             Number,                 NULL,        0, ""},
63         {{HKEY("label")},           FlatString,             NULL,        0, ""},
64         {{HKEY("uid")},             FlatString,             NULL,        0, ""},
65         {{HKEY("n")},               StringCluster,          VCStr_Ns,    0, ""}, /* N is name, but only if there's no FN already there */
66         {{HKEY("fn")},              FlatString,             NULL,        0, ""}, /* FN (full name) is a true 'display name' field */
67         {{HKEY("title")},           FlatString,             NULL,        0, "Title:"},
68         {{HKEY("org")},             FlatString,             NULL,        0, "Organization:"},/* organization */
69         {{HKEY("email")},           EmailAddr,              NULL,        0, "E-mail:"},
70         {{HKEY("tel")},             PhoneNumber,            NULL,        0, "Telephone:"},
71         {{HKEY("adr")},             StringCluster,          VCStr_Addrs, 0, "Address:"},
72         {{HKEY("photo")},           Base64BinaryAttachment, NULL,        0, "Photo:"},
73         {{HKEY("tel;home")},        PhoneNumber,            NULL,        0, " (home)"},
74         {{HKEY("tel;work")},        PhoneNumber,            NULL,        0, " (work)"},
75         {{HKEY("tel;fax")},         PhoneNumber,            NULL,        0, " (fax)"},
76         {{HKEY("tel;cell")},        PhoneNumber,            NULL,        0, " (cell)"},
77         {{HKEY("email;internet")},  EmailAddr,              NULL,        0, "E-mail:"},
78         {{HKEY("")},                TerminateList,          NULL,        0, ""}
79 };
80
81 ConstStr VCStr [] = {
82         {HKEY("")},
83         {HKEY("n")}, /* N is name, but only if there's no FN already there */
84         {HKEY("fn")}, /* FN (full name) is a true 'display name' field */
85         {HKEY("title")},   /* title */
86         {HKEY("org")},    /* organization */
87         {HKEY("email")},
88         {HKEY("tel")},
89         {HKEY("work")},
90         {HKEY("home")},
91         {HKEY("cell")},
92         {HKEY("adr")},
93         {HKEY("photo")},
94         {HKEY("version")},
95         {HKEY("rev")},
96         {HKEY("label")},
97         {HKEY("uid")}
98 };
99
100
101 HashList *DefineToToken = NULL;
102 HashList *VCTokenToDefine = NULL;
103 HashList *vcNames = NULL; /* todo: fill with the name strings */
104
105
106 void RegisterVCardToken(vcField* vf, StrBuf *name, int inTokenCount)
107 {
108         RegisterTokenParamDefine(SKEY(name), vf->cval);
109         Put(DefineToToken, LKEY(vf->cval), vf, reference_free_handler);
110
111         syslog(LOG_DEBUG, "Token: %s -> %ld, %d", 
112                ChrPtr(name),
113                vf->cval, 
114                inTokenCount);
115
116 }
117
118 void autoRegisterTokens(long *enumCounter, vcField* vf, StrBuf *BaseStr, int layer)
119 {
120         int i = 0;
121         while (vf[i].STR.len > 0) {
122                 StrBuf *subStr = NewStrBuf();
123                 vf[i].cval = (*enumCounter) ++;
124                 StrBufAppendBuf(subStr, BaseStr, 0);
125                 if (StrLength(subStr) > 0) {
126                         StrBufAppendBufPlain(subStr, HKEY("."), 0);
127                 }
128                 StrBufAppendBufPlain(subStr, CKEY(vf[i].STR), 0);
129                 if (layer == 0) {
130                         Put(VCTokenToDefine, CKEY(vf[i].STR), &vf[i], reference_free_handler);
131                 }
132                 switch (vf[i].Type) {
133                 case FlatString:
134                         break;
135                 case StringCluster:
136                 {
137                         autoRegisterTokens(enumCounter, vf[i].Sub, subStr, 1);
138                         i++;
139                         continue;
140                 }
141                 break;
142                 case PhoneNumber:
143                         break;
144                 case EmailAddr:
145                         break;
146                 case Street:
147                         break;
148                 case Number:
149                         break;
150                 case AliasFor:
151                         break;
152                 case Base64BinaryAttachment:
153                         break;
154                 case TerminateList:
155                         break;
156                 }
157                 RegisterVCardToken(&vf[i], subStr, i);
158                 i++;
159         }
160 }
161
162 int preeval_vcard_item(WCTemplateToken *Token)
163 {
164         WCTemplputParams TPP;
165         WCTemplputParams *TP;
166         int searchFieldNo;
167         StrBuf *Target = NULL;
168
169         memset(&TPP, 0, sizeof(WCTemplputParams));
170         TP = &TPP;
171         TP->Tokens = Token;
172         searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
173         if (searchFieldNo >= VCEnumCounter) {
174                 LogTemplateError(NULL, "VCardItem", ERR_PARM1, TP,
175                                  "Invalid define");
176                 return 0;
177         }
178         return 1;
179 }
180
181 void tmpl_vcard_item(StrBuf *Target, WCTemplputParams *TP)
182 {
183         void *vItem;
184         long searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
185         HashList *vc = (HashList*) CTX(CTX_VCARD);
186         if (GetHash(vc, LKEY(searchFieldNo), &vItem) && (vItem != NULL)) {
187                 StrBufAppendTemplate(Target, TP, (StrBuf*) vItem, 1);
188         }
189 }
190
191 int preeval_vcard_name_str(WCTemplateToken *Token)
192 {
193         WCTemplputParams TPP;
194         WCTemplputParams *TP;
195         int searchFieldNo;
196         StrBuf *Target = NULL;
197
198         memset(&TPP, 0, sizeof(WCTemplputParams));
199         TP = &TPP;
200         TP->Tokens = Token;
201         searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
202         if (searchFieldNo >= VCEnumCounter) {
203                 LogTemplateError(NULL, "VCardName", ERR_PARM1, TP,
204                                  "Invalid define");
205                 return 0;
206         }
207         return 1;
208 }
209
210 void tmpl_vcard_name_str(StrBuf *Target, WCTemplputParams *TP)
211 {
212         void *vItem;
213         long searchFieldNo = GetTemplateTokenNumber(Target, TP, 0, 0);
214         /* todo: get descriptive string for this vcard type */
215         if (GetHash(vcNames, LKEY(searchFieldNo), &vItem) && (vItem != NULL)) {
216                 StrBufAppendTemplate(Target, TP, (StrBuf*) vItem, 1);
217         }
218 }
219
220 int filter_VC_ByType(const char* key, long len, void *Context, StrBuf *Target, WCTemplputParams *TP)
221 {
222         long searchType;
223         long type = 0;
224         void *vvcField;
225         int rc = 0;
226
227         memcpy(type, key, sizeof(long));
228         searchType = GetTemplateTokenNumber(Target, TP, 3, 0);/// todo: which?
229         
230         if (GetHash(DefineToToken, LKEY(type), &vvcField) &&
231             (vvcField != NULL))
232         {
233                 vcField *t = (vcField*) vvcField;
234                 if (t && t->Type == searchType) {
235                         rc = 1; 
236                 }
237         }
238         return rc;
239 }
240
241 int conditional_VC_Havetype(StrBuf *Target, WCTemplputParams *TP)
242 {
243         HashList *vc = (HashList*) CTX(CTX_VCARD);
244         long HaveFieldType = GetTemplateTokenNumber(Target, TP, 2, 0);
245         int rc = 0;     
246         void *vVCitem;
247         const char *Key;
248         long len;
249         HashPos *it = GetNewHashPos(vc, 0);
250         while (GetNextHashPos(vc, it, &len, &Key, &vVCitem) && 
251                (vVCitem != NULL)) 
252         {
253                 void *vvcField;
254                 long type = 0;
255                 memcpy(&type, Key, sizeof(long));
256                 if (GetHash(DefineToToken, LKEY(type), &vvcField) &&
257                     (vvcField != NULL))
258                 {
259                         vcField *t = (vcField*) vvcField;
260                         if (t && t->Type == HaveFieldType) {
261                                 rc = 1;
262                                 break;
263                         }
264                 }
265         }
266         DeleteHashPos(&it);
267         return rc;
268 }
269
270 /*
271  * Record compare function for sorting address book indices
272  */
273 int abcmp(const void *ab1, const void *ab2) {
274         return(strcasecmp(
275                 (((const addrbookent *)ab1)->ab_name),
276                 (((const addrbookent *)ab2)->ab_name)
277         ));
278 }
279
280
281 /*
282  * Helper function for do_addrbook_view()
283  * Converts a name into a three-letter tab label
284  */
285 void nametab(char *tabbuf, long len, char *name) {
286         stresc(tabbuf, len, name, 0, 0);
287         tabbuf[0] = toupper(tabbuf[0]);
288         tabbuf[1] = tolower(tabbuf[1]);
289         tabbuf[2] = tolower(tabbuf[2]);
290         tabbuf[3] = 0;
291 }
292
293
294 /*
295  * If it's an old "Firstname Lastname" style record, try to convert it.
296  */
297 void lastfirst_firstlast(char *namebuf) {
298         char firstname[SIZ];
299         char lastname[SIZ];
300         int i;
301
302         if (namebuf == NULL) return;
303         if (strchr(namebuf, ';') != NULL) return;
304
305         i = num_tokens(namebuf, ' ');
306         if (i < 2) return;
307
308         extract_token(lastname, namebuf, i-1, ' ', sizeof lastname);
309         remove_token(namebuf, i-1, ' ');
310         strcpy(firstname, namebuf);
311         sprintf(namebuf, "%s; %s", lastname, firstname);
312 }
313
314
315
316 wc_mime_attachment *load_vcard(message_summary *Msg) 
317 {
318         HashPos  *it;
319         StrBuf *FoundCharset = NewStrBuf();
320         StrBuf *Error;
321         void *vMime;
322         const char *Key;
323         long len;
324         wc_mime_attachment *Mime;
325         wc_mime_attachment *VCMime = NULL;
326
327         Msg->MsgBody =  (wc_mime_attachment*) malloc(sizeof(wc_mime_attachment));
328         memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
329         Msg->MsgBody->msgnum = Msg->msgnum;
330
331         load_message(Msg, FoundCharset, &Error);
332
333         FreeStrBuf(&FoundCharset);
334         /* look up the vcard... */
335         it = GetNewHashPos(Msg->AllAttach, 0);
336         while (GetNextHashPos(Msg->AllAttach, it, &len, &Key, &vMime) && 
337                (vMime != NULL)) 
338         {
339                 Mime = (wc_mime_attachment*) vMime;
340                 if ((strcmp(ChrPtr(Mime->ContentType),
341                            "text/x-vcard") == 0) ||
342                     (strcmp(ChrPtr(Mime->ContentType),
343                             "text/vcard") == 0))
344                 {
345                         VCMime = Mime;
346                         break;
347                 }
348         }
349         DeleteHashPos(&it);
350         if (VCMime == NULL)
351                 return NULL;
352
353         if (VCMime->Data == NULL)
354                 MimeLoadData(VCMime);
355         return VCMime;
356 }
357
358 /*
359  * fetch the display name off a vCard
360  */
361 void fetch_ab_name(message_summary *Msg, char **namebuf) {
362         long len;
363         int i;
364         wc_mime_attachment *VCMime = NULL;
365
366         if (namebuf == NULL) return;
367
368         VCMime = load_vcard(Msg);
369         if (VCMime == NULL)
370                 return;
371
372         /* Grab the name off the card */
373         display_vcard(WC->WBuf, VCMime, 0, 0, namebuf, Msg->msgnum);
374
375         if (*namebuf != NULL) {
376                 lastfirst_firstlast(*namebuf);
377                 striplt(*namebuf);
378                 len = strlen(*namebuf);
379                 for (i=0; i<len; ++i) {
380                         if ((*namebuf)[i] != ';') return;
381                 }
382                 free (*namebuf);
383                 (*namebuf) = strdup(_("(no name)"));
384         }
385         else {
386                 (*namebuf) = strdup(_("(no name)"));
387         }
388 }
389
390
391
392 /*
393  * Turn a vCard "n" (name) field into something displayable.
394  */
395 void vcard_n_prettyize(char *name)
396 {
397         char *original_name;
398         int i, j, len;
399
400         original_name = strdup(name);
401         len = strlen(original_name);
402         for (i=0; i<5; ++i) {
403                 if (len > 0) {
404                         if (original_name[len-1] == ' ') {
405                                 original_name[--len] = 0;
406                         }
407                         if (original_name[len-1] == ';') {
408                                 original_name[--len] = 0;
409                         }
410                 }
411         }
412         strcpy(name, "");
413         j=0;
414         for (i=0; i<len; ++i) {
415                 if (original_name[i] == ';') {
416                         name[j++] = ',';
417                         name[j++] = ' ';                        
418                 }
419                 else {
420                         name[j++] = original_name[i];
421                 }
422         }
423         name[j] = '\0';
424         free(original_name);
425 }
426
427
428
429
430 /*
431  * preparse a vcard name
432  * display_vcard() calls this after parsing the textual vCard into
433  * our 'struct vCard' data object.
434  * This gets called instead of display_parsed_vcard() if we are only looking
435  * to extract the person's name instead of displaying the card.
436  */
437 void fetchname_parsed_vcard(struct vCard *v, char **storename) {
438         char *name;
439         char *prop;
440         char buf[SIZ];
441         int j, n, len;
442         int is_qp = 0;
443         int is_b64 = 0;
444
445         *storename = NULL;
446
447         name = vcard_get_prop(v, "n", 1, 0, 0);
448         if (name != NULL) {
449                 len = strlen(name);
450                 prop = vcard_get_prop(v, "n", 1, 0, 1);
451                 n = num_tokens(prop, ';');
452
453                 for (j=0; j<n; ++j) {
454                         extract_token(buf, prop, j, ';', sizeof buf);
455                         if (!strcasecmp(buf, "encoding=quoted-printable")) {
456                                 is_qp = 1;
457                         }
458                         if (!strcasecmp(buf, "encoding=base64")) {
459                                 is_b64 = 1;
460                         }
461                 }
462                 if (is_qp) {
463                         /* %ff can become 6 bytes in utf8  */
464                         *storename = malloc(len * 2 + 3); 
465                         j = CtdlDecodeQuotedPrintable(
466                                 *storename, name,
467                                 len);
468                         (*storename)[j] = 0;
469                 }
470                 else if (is_b64) {
471                         /* ff will become one byte.. */
472                         *storename = malloc(len + 50);
473                         CtdlDecodeBase64(
474                                 *storename, name,
475                                 len);
476                 }
477                 else {
478                         size_t len;
479
480                         len = strlen (name);
481                         
482                         *storename = malloc(len + 3); /* \0 + eventualy missing ', '*/
483                         memcpy(*storename, name, len + 1);
484                 }
485                 /* vcard_n_prettyize(storename); */
486         }
487
488 }
489
490
491
492 /*
493  * html print a vcard
494  * display_vcard() calls this after parsing the textual vCard into
495  * our 'struct vCard' data object.
496  *
497  * Set 'full' to nonzero to display the full card, otherwise it will only
498  * show a summary line.
499  *
500  * This code is a bit ugly, so perhaps an explanation is due: we do this
501  * in two passes through the vCard fields.  On the first pass, we process
502  * fields we understand, and then render them in a pretty fashion at the
503  * end.  Then we make a second pass, outputting all the fields we don't
504  * understand in a simple two-column name/value format.
505  * v            the vCard to display
506  * full         display all items of the vcard?
507  * msgnum       Citadel message pointer
508  */
509 void display_parsed_vcard(StrBuf *Target, struct vCard *v, int full, wc_mime_attachment *Mime)
510 {
511         int i, j;
512         char buf[SIZ];
513         char *name;
514         int is_qp = 0;
515         int is_b64 = 0;
516         char *thisname, *thisvalue;
517         char firsttoken[SIZ];
518         int pass;
519
520         char fullname[SIZ];
521         char title[SIZ];
522         char org[SIZ];
523         char phone[SIZ];
524         char mailto[SIZ];
525
526         strcpy(fullname, "");
527         strcpy(phone, "");
528         strcpy(mailto, "");
529         strcpy(title, "");
530         strcpy(org, "");
531
532         if (!full) {
533                 StrBufAppendPrintf(Target, "<td>");
534                 name = vcard_get_prop(v, "fn", 1, 0, 0);
535                 if (name != NULL) {
536                         StrEscAppend(Target, NULL, name, 0, 0);
537                 }
538                 else if (name = vcard_get_prop(v, "n", 1, 0, 0), name != NULL) {
539                         strcpy(fullname, name);
540                         vcard_n_prettyize(fullname);
541                         StrEscAppend(Target, NULL, fullname, 0, 0);
542                 }
543                 else {
544                         StrBufAppendPrintf(Target, "&nbsp;");
545                 }
546                 StrBufAppendPrintf(Target, "</td>");
547                 return;
548         }
549
550         StrBufAppendPrintf(Target, "<div align=\"center\">"
551                 "<table bgcolor=\"#aaaaaa\" width=\"50%%\">");
552         for (pass=1; pass<=2; ++pass) {
553
554                 if (v->numprops) for (i=0; i<(v->numprops); ++i) {
555                         int len;
556                         thisname = strdup(v->prop[i].name);
557                         extract_token(firsttoken, thisname, 0, ';', sizeof firsttoken);
558         
559                         for (j=0; j<num_tokens(thisname, ';'); ++j) {
560                                 extract_token(buf, thisname, j, ';', sizeof buf);
561                                 if (!strcasecmp(buf, "encoding=quoted-printable")) {
562                                         is_qp = 1;
563                                         remove_token(thisname, j, ';');
564                                 }
565                                 if (!strcasecmp(buf, "encoding=base64")) {
566                                         is_b64 = 1;
567                                         remove_token(thisname, j, ';');
568                                 }
569                         }
570                         
571                         len = strlen(v->prop[i].value);
572                         /* if we have some untagged QP, detect it here. */
573                         if (!is_qp && (strstr(v->prop[i].value, "=?")!=NULL))
574                                 utf8ify_rfc822_string(&v->prop[i].value);
575
576                         if (is_qp) {
577                                 /* %ff can become 6 bytes in utf8 */
578                                 thisvalue = malloc(len * 2 + 3); 
579                                 j = CtdlDecodeQuotedPrintable(
580                                         thisvalue, v->prop[i].value,
581                                         len);
582                                 thisvalue[j] = 0;
583                         }
584                         else if (is_b64) {
585                                 /* ff will become one byte.. */
586                                 thisvalue = malloc(len + 50);
587                                 CtdlDecodeBase64(
588                                         thisvalue, v->prop[i].value,
589                                         strlen(v->prop[i].value) );
590                         }
591                         else {
592                                 thisvalue = strdup(v->prop[i].value);
593                         }
594         
595                         /* Various fields we may encounter ***/
596         
597                         /* N is name, but only if there's no FN already there */
598                         if (!strcasecmp(firsttoken, "n")) {
599                                 if (IsEmptyStr(fullname)) {
600                                         strcpy(fullname, thisvalue);
601                                         vcard_n_prettyize(fullname);
602                                 }
603                         }
604         
605                         /* FN (full name) is a true 'display name' field */
606                         else if (!strcasecmp(firsttoken, "fn")) {
607                                 strcpy(fullname, thisvalue);
608                         }
609
610                         /* title */
611                         else if (!strcasecmp(firsttoken, "title")) {
612                                 strcpy(title, thisvalue);
613                         }
614         
615                         /* organization */
616                         else if (!strcasecmp(firsttoken, "org")) {
617                                 strcpy(org, thisvalue);
618                         }
619         
620                         else if (!strcasecmp(firsttoken, "email")) {
621                                 size_t len;
622                                 if (!IsEmptyStr(mailto)) strcat(mailto, "<br>");
623                                 strcat(mailto,
624                                         "<a href=\"display_enter"
625                                         "?force_room=_MAIL_?recp=");
626
627                                 len = strlen(mailto);
628                                 urlesc(&mailto[len], SIZ - len, "\"");
629                                 len = strlen(mailto);
630                                 urlesc(&mailto[len], SIZ - len,  fullname);
631                                 len = strlen(mailto);
632                                 urlesc(&mailto[len], SIZ - len, "\" <");
633                                 len = strlen(mailto);
634                                 urlesc(&mailto[len], SIZ - len, thisvalue);
635                                 len = strlen(mailto);
636                                 urlesc(&mailto[len], SIZ - len, ">");
637
638                                 strcat(mailto, "\">");
639                                 len = strlen(mailto);
640                                 stresc(mailto+len, SIZ - len, thisvalue, 1, 1);
641                                 strcat(mailto, "</A>");
642                         }
643                         else if (!strcasecmp(firsttoken, "tel")) {
644                                 if (!IsEmptyStr(phone)) strcat(phone, "<br>");
645                                 strcat(phone, thisvalue);
646                                 for (j=0; j<num_tokens(thisname, ';'); ++j) {
647                                         extract_token(buf, thisname, j, ';', sizeof buf);
648                                         if (!strcasecmp(buf, "tel"))
649                                                 strcat(phone, "");
650                                         else if (!strcasecmp(buf, "work"))
651                                                 strcat(phone, _(" (work)"));
652                                         else if (!strcasecmp(buf, "home"))
653                                                 strcat(phone, _(" (home)"));
654                                         else if (!strcasecmp(buf, "cell"))
655                                                 strcat(phone, _(" (cell)"));
656                                         else {
657                                                 strcat(phone, " (");
658                                                 strcat(phone, buf);
659                                                 strcat(phone, ")");
660                                         }
661                                 }
662                         }
663                         else if (!strcasecmp(firsttoken, "adr")) {
664                                 if (pass == 2) {
665                                         StrBufAppendPrintf(Target, "<tr><td>");
666                                         StrBufAppendPrintf(Target, _("Address:"));
667                                         StrBufAppendPrintf(Target, "</td><td>");
668                                         for (j=0; j<num_tokens(thisvalue, ';'); ++j) {
669                                                 extract_token(buf, thisvalue, j, ';', sizeof buf);
670                                                 if (!IsEmptyStr(buf)) {
671                                                         StrEscAppend(Target, NULL, buf, 0, 0);
672                                                         if (j<3) StrBufAppendPrintf(Target, "<br>");
673                                                         else StrBufAppendPrintf(Target, " ");
674                                                 }
675                                         }
676                                         StrBufAppendPrintf(Target, "</td></tr>\n");
677                                 }
678                         }
679                         /* else if (!strcasecmp(firsttoken, "photo") && full && pass == 2) { 
680                                 // Only output on second pass
681                                 StrBufAppendPrintf(Target, "<tr><td>");
682                                 StrBufAppendPrintf(Target, _("Photo:"));
683                                 StrBufAppendPrintf(Target, "</td><td>");
684                                 StrBufAppendPrintf(Target, "<img src=\"/vcardphoto/%ld/\" alt=\"Contact photo\"/>",msgnum);
685                                 StrBufAppendPrintf(Target, "</td></tr>\n");
686                         } */
687                         else if (!strcasecmp(firsttoken, "version")) {
688                                 /* ignore */
689                         }
690                         else if (!strcasecmp(firsttoken, "rev")) {
691                                 /* ignore */
692                         }
693                         else if (!strcasecmp(firsttoken, "label")) {
694                                 /* ignore */
695                         }
696                         else {
697
698                                 /*** Don't show extra fields.  They're ugly.
699                                 if (pass == 2) {
700                                         StrBufAppendPrintf(Target, "<TR><TD>");
701                                         StrEscAppend(Target, NULL, thisname, 0, 0);
702                                         StrBufAppendPrintf(Target, "</TD><TD>");
703                                         StrEscAppend(Target, NULL, thisvalue, 0, 0);
704                                         StrBufAppendPrintf(Target, "</TD></TR>\n");
705                                 }
706                                 ***/
707                         }
708         
709                         free(thisname);
710                         free(thisvalue);
711                 }
712         
713                 if (pass == 1) {
714                         StrBufAppendPrintf(Target, "<tr bgcolor=\"#aaaaaa\">"
715       "<td colspan=2 bgcolor=\"#ffffff\">"
716       "<img align=\"center\" src=\"static/webcit_icons/essen/32x32/contact.png\">"
717       "<font size=\"+1\"><b>");
718                         StrEscAppend(Target, NULL, fullname, 0, 0);
719                         StrBufAppendPrintf(Target, "</b></font>");
720                         if (!IsEmptyStr(title)) {
721                                 StrBufAppendPrintf(Target, "<div align=\"right>\"");
722                                 StrEscAppend(Target, NULL, title, 0, 0);
723                                 StrBufAppendPrintf(Target, "</div>");
724                         }
725                         if (!IsEmptyStr(org)) {
726                                 StrBufAppendPrintf(Target, "<div align=\"right\">");
727                                 StrEscAppend(Target, NULL, org, 0, 0);
728                                 StrBufAppendPrintf(Target, "</div>");
729                         }
730                         StrBufAppendPrintf(Target, "</td></tr>\n");
731                 
732                         if (!IsEmptyStr(phone)) {
733                                 StrBufAppendPrintf(Target, "<tr><td>");
734                                 StrBufAppendPrintf(Target, _("Telephone:"));
735                                 StrBufAppendPrintf(Target, "</td><td>%s</td></tr>\n", phone);
736                         }
737                         if (!IsEmptyStr(mailto)) {
738                                 StrBufAppendPrintf(Target, "<tr><td>");
739                                 StrBufAppendPrintf(Target, _("E-mail:"));
740                                 StrBufAppendPrintf(Target, "</td><td>%s</td></tr>\n", mailto);
741                         }
742                 }
743
744         }
745
746         StrBufAppendPrintf(Target, "</table></div>\n");
747 }
748
749
750 void PutVcardItem(vcField *thisField, HashList *thisVC, StrBuf *ThisFieldStr, int is_qp, StrBuf *Swap)
751 {
752         /* if we have some untagged QP, detect it here. */
753         if (is_qp || (strstr(ChrPtr(ThisFieldStr), "=?")!=NULL)){
754                 StrBuf *b;
755                 StrBuf_RFC822_to_Utf8(Swap, ThisFieldStr, NULL, NULL); /* default charset, current charset */
756                 b = ThisFieldStr;
757                 ThisFieldStr = Swap; 
758                 Swap = b;
759                 FlushStrBuf(Swap);
760         }
761         Put(thisVC, LKEY(thisField->cval), ThisFieldStr, HFreeStrBuf);
762 }
763 /*
764  * html print a vcard
765  * display_vcard() calls this after parsing the textual vCard into
766  * our 'struct vCard' data object.
767  *
768  * Set 'full' to nonzero to display the full card, otherwise it will only
769  * show a summary line.
770  *
771  * This code is a bit ugly, so perhaps an explanation is due: we do this
772  * in two passes through the vCard fields.  On the first pass, we process
773  * fields we understand, and then render them in a pretty fashion at the
774  * end.  Then we make a second pass, outputting all the fields we don't
775  * understand in a simple two-column name/value format.
776  * v            the vCard to display
777  * full         display all items of the vcard?
778  * msgnum       Citadel message pointer
779  */
780 void parse_vcard(StrBuf *Target, struct vCard *v, HashList *VC, int full, wc_mime_attachment *Mime)
781 {
782         StrBuf *Val = NULL;
783         StrBuf *Swap = NULL;
784         int i, j, k;
785         char buf[20]; //SIZ];
786         int is_qp = 0;
787         int is_b64 = 0;
788         int ntokens, len;
789         StrBuf *thisname = NULL;
790         char firsttoken[20]; ///SIZ];
791         void *V;
792         HashList *thisVC;
793         StrBuf *thisVCToken;
794         void *vField = NULL;
795
796         thisVC = NewHash(1, lFlathash);
797         Swap = NewStrBuf ();
798         thisname = NewStrBuf();
799         thisVCToken = NewStrBufPlain(NULL, 63);
800         for (i=0; i<(v->numprops); ++i) {
801                 FlushStrBuf(thisVCToken);
802                 is_qp = 0;
803                 is_b64 = 0;
804                 syslog(LOG_DEBUG, "i: %d oneprop: %s - value: %s", i, v->prop[i].name, v->prop[i].value);
805                 StrBufPlain(thisname, v->prop[i].name, -1);
806                 StrBufLowerCase(thisname);
807                 
808                 /*len = */extract_token(firsttoken, ChrPtr(thisname), 0, ';', sizeof firsttoken);
809                 ntokens = num_tokens(ChrPtr(thisname), ';');
810                 for (j=0, k=0; j < ntokens && k < 10; ++j) {
811                         int evc[10];
812                         
813                         len = extract_token(buf, ChrPtr(thisname), j, ';', sizeof buf);
814                         if (!strcasecmp(buf, "encoding=quoted-printable")) {
815                                 is_qp = 1;
816 /*                              remove_token(thisname, j, ';');*/
817                         }
818                         else if (!strcasecmp(buf, "encoding=base64")) {
819                                 is_b64 = 1;
820 /*                              remove_token(thisname, j, ';');*/
821                         }
822                         else{
823                                 if (StrLength(thisVCToken) > 0) {
824                                         StrBufAppendBufPlain(thisVCToken, HKEY(";"), 0);
825                                 }
826                                 StrBufAppendBufPlain(thisVCToken, buf, len, 0);
827                                 /*
828                                 if (GetHash(VCToEnum, buf, len, &V))
829                                 {
830                                         evc[k] = (int) V;
831
832                                         Put(VC, IKEY(evc), Val, HFreeStrBuf);
833
834                                         syslog(LOG_DEBUG, "[%ul] -> k: %d %s - %s", evc, k, buf, VCStr[evc[k]].Key);
835                                         k++;
836                                 }
837 */
838
839                         }
840                 }
841
842                 vField = NULL;  
843                 if ((StrLength(thisVCToken) > 0) &&
844                     GetHash(VCTokenToDefine, SKEY(thisVCToken), &vField) && 
845                     (vField != NULL)) {
846                         vcField *thisField = (vcField *)vField;
847                         StrBuf *ThisFieldStr = NULL;
848                         syslog(LOG_DEBUG, "got this token: %s, found: %s", ChrPtr(thisVCToken), thisField->STR.Key);
849                         switch (thisField->Type) {
850                         case StringCluster: {
851                                 int j = 0;
852                                 const char *Pos = NULL;
853                                 StrBuf *thisArray = NewStrBufPlain(v->prop[i].value, -1);
854                                 StrBuf *Buf = NewStrBufPlain(NULL, StrLength(thisArray));
855                                 while (thisField->Sub[j].STR.len > 0) {
856                                         StrBufExtract_NextToken(Buf, thisArray, &Pos, ';');
857                                         ThisFieldStr = NewStrBufDup(Buf);
858                                         
859                                         PutVcardItem(&thisField->Sub[j], thisVC, ThisFieldStr, is_qp, Swap);
860                                         j++;
861                                 }
862                         }
863                                 break;
864                         case FlatString:
865                         case PhoneNumber:
866                         case EmailAddr:
867                         case Street:
868                         case Number:
869                         case AliasFor:
870                                 /* copy over the payload into a StrBuf */
871                                 ThisFieldStr = NewStrBufPlain(v->prop[i].value, -1);
872                                 PutVcardItem(thisField, thisVC, ThisFieldStr, is_qp, Swap);
873
874                                 break;
875                         case Base64BinaryAttachment:
876                         case TerminateList:
877                                 break;
878                         }
879
880                 }
881                 /* copy over the payload into a StrBuf */
882                 Val = NewStrBufPlain(v->prop[i].value, -1);
883                         
884                 /* if we have some untagged QP, detect it here. */
885                 if (is_qp || (strstr(v->prop[i].value, "=?")!=NULL)){
886                         StrBuf *b;
887                         StrBuf_RFC822_to_Utf8(Swap, Val, NULL, NULL); /* default charset, current charset */
888                         b = Val;
889                         Val = Swap; 
890                         Swap = b;
891                         FlushStrBuf(Swap);
892                 }
893                 else if (is_b64) {
894                         StrBufDecodeBase64(Val);
895
896                 }
897 #if 0
898                 syslog(LOG_DEBUG, "-> firsttoken: %s thisname: %s Value: [%s][%s]",
899                         firsttoken,
900                        ChrPtr(thisname),
901                         ChrPtr(Val),
902                         v->prop[i].value);
903                 if (GetHash(VCToEnum, firsttoken, strlen(firsttoken), &V))
904                 {
905                         eVC evc = (eVC) V;
906                         Put(VC, IKEY(evc), Val, HFreeStrBuf);
907                         syslog(LOG_DEBUG, "[%ul]\n", evc);
908                         Val = NULL;
909                 }
910                 else
911                         syslog(LOG_DEBUG, "[]\n");
912 /*
913 TODO: check for layer II
914                 else 
915                 {
916                         long max = num_tokens(thisname, ';');
917                         firsttoken[len] = '_';
918
919                         for (j = 0; j < max; j++) {
920 //                      firsttoken[len]
921
922                                 extract_token(buf, thisname, j, ';', sizeof (buf));
923                                         if (!strcasecmp(buf, "tel"))
924                                                 strcat(phone, "");
925                                         else if (!strcasecmp(buf, "work"))
926                                                 strcat(phone, _(" (work)"));
927                                         else if (!strcasecmp(buf, "home"))
928                                                 strcat(phone, _(" (home)"));
929                                         else if (!strcasecmp(buf, "cell"))
930                                                 strcat(phone, _(" (cell)"));
931                                         else {
932                                                 strcat(phone, " (");
933                                                 strcat(phone, buf);
934                                                 strcat(phone, ")");
935                                         }
936                                 }
937                         }
938
939                 }
940 */
941 #endif  
942                 FreeStrBuf(&Val);
943                 ////free(thisname);
944                 /// thisname = NULL;
945         }
946
947
948         {
949                 WCTemplputParams *TP = NULL;
950                 WCTemplputParams SubTP;
951                 FlushStrBuf(Target);
952                 StackContext(TP, &SubTP, thisVC, CTX_VCARD, 0, NULL);
953                 {
954                         DoTemplate(HKEY("test_vcard"), Target, &SubTP);
955                 }
956                 UnStackContext(&SubTP);
957         }
958         printf("%s\n", ChrPtr(Target));
959         FreeStrBuf(&thisVCToken);
960         DeleteHash(&thisVC);/// todo
961 }
962
963 void tmplput_VCARD_ITEM(StrBuf *Target, WCTemplputParams *TP)
964 {
965         HashList *VC = CTX(CTX_VCARD);
966         int evc;
967         void *vStr;
968
969         evc = GetTemplateTokenNumber(Target, TP, 0, -1);
970         if (evc != -1)
971         {
972                 if (GetHash(VC, IKEY(evc), &vStr))
973                 {
974                         StrBufAppendTemplate(Target, TP,
975                                              (StrBuf*) vStr,
976                                              1);
977                 }
978         }
979         
980 }
981
982 void new_vcard (StrBuf *Target, struct vCard *v, int full, wc_mime_attachment *Mime)
983 {
984         HashList *VC;
985         WCTemplputParams SubTP;
986
987         memset(&SubTP, 0, sizeof(WCTemplputParams));    
988
989
990         VC = NewHash(0, Flathash);
991         parse_vcard(Target, v, VC, full, Mime);
992
993         SubTP.Filter.ContextType = CTX_VCARD;
994         SubTP.Context = VC;
995
996         DoTemplate(HKEY("test_vcard"), Target, &SubTP);
997         DeleteHash(&VC);
998 }
999
1000
1001
1002 /*
1003  * Display a textual vCard
1004  * (Converts to a vCard object and then calls the actual display function)
1005  * Set 'full' to nonzero to display the whole card instead of a one-liner.
1006  * Or, if "storename" is non-NULL, just store the person's name in that
1007  * buffer instead of displaying the card at all.
1008  *
1009  * vcard_source the buffer containing the vcard text
1010  * alpha        Display only if name begins with this letter of the alphabet
1011  * full         Display the full vCard (otherwise just the display name)
1012  * storename    If not NULL, also store the display name here
1013  * msgnum       Citadel message pointer
1014  */
1015 void display_vcard(StrBuf *Target, 
1016                    wc_mime_attachment *Mime, 
1017                    char alpha, 
1018                    int full, 
1019                    char **storename, 
1020                    long msgnum) 
1021 {
1022         struct vCard *v;
1023         char *name;
1024         StrBuf *Buf;
1025         StrBuf *Buf2;
1026         char this_alpha = 0;
1027
1028         v = VCardLoad(Mime->Data);
1029
1030         if (v == NULL) return;
1031
1032         name = vcard_get_prop(v, "n", 1, 0, 0);
1033         if (name != NULL) {
1034                 Buf = NewStrBufPlain(name, -1);
1035                 Buf2 = NewStrBufPlain(NULL, StrLength(Buf));
1036                 StrBuf_RFC822_to_Utf8(Buf2, Buf, WC->DefaultCharset, NULL);
1037                 this_alpha = ChrPtr(Buf)[0];
1038                 FreeStrBuf(&Buf);
1039                 FreeStrBuf(&Buf2);
1040         }
1041
1042         if (storename != NULL) {
1043                 fetchname_parsed_vcard(v, storename);
1044         }
1045         else if ((alpha == 0) || 
1046                  ((isalpha(alpha)) && (tolower(alpha) == tolower(this_alpha))) || 
1047                  ((!isalpha(alpha)) && (!isalpha(this_alpha)))
1048                 ) 
1049         {
1050 #define XXX_XXX 1
1051 #ifdef XXX_XXX
1052                 new_vcard (Target, v, full, Mime);
1053 #else
1054                 display_parsed_vcard(Target, v, full, Mime);
1055 #endif          
1056         }
1057
1058         vcard_free(v);
1059 }
1060
1061
1062
1063 /*
1064  * Render the address book using info we gathered during the scan
1065  *
1066  * addrbook     the addressbook to render
1067  * num_ab       the number of the addressbook
1068  */
1069 void do_addrbook_view(addrbookent *addrbook, int num_ab) {
1070         int i = 0;
1071         int displayed = 0;
1072         int bg = 0;
1073         static int NAMESPERPAGE = 60;
1074         int num_pages = 0;
1075         int tabfirst = 0;
1076         char tabfirst_label[64];
1077         int tablast = 0;
1078         char tablast_label[64];
1079         char this_tablabel[64];
1080         int page = 0;
1081         char **tablabels;
1082
1083         if (num_ab == 0) {
1084                 wc_printf("<br><br><br><div align=\"center\"><i>");
1085                 wc_printf(_("This address book is empty."));
1086                 wc_printf("</i></div>\n");
1087                 return;
1088         }
1089
1090         if (num_ab > 1) {
1091                 qsort(addrbook, num_ab, sizeof(addrbookent), abcmp);
1092         }
1093
1094         num_pages = (num_ab / NAMESPERPAGE) + 1;
1095
1096         tablabels = malloc(num_pages * sizeof (char *));
1097         if (tablabels == NULL) {
1098                 wc_printf("<br><br><br><div align=\"center\"><i>");
1099                 wc_printf(_("An internal error has occurred."));
1100                 wc_printf("</i></div>\n");
1101                 return;
1102         }
1103
1104         for (i=0; i<num_pages; ++i) {
1105                 tabfirst = i * NAMESPERPAGE;
1106                 tablast = tabfirst + NAMESPERPAGE - 1;
1107                 if (tablast > (num_ab - 1)) tablast = (num_ab - 1);
1108                 nametab(tabfirst_label, 64, addrbook[tabfirst].ab_name);
1109                 nametab(tablast_label, 64, addrbook[tablast].ab_name);
1110                 sprintf(this_tablabel, "%s&nbsp;-&nbsp;%s", tabfirst_label, tablast_label);
1111                 tablabels[i] = strdup(this_tablabel);
1112         }
1113
1114         tabbed_dialog(num_pages, tablabels);
1115         page = (-1);
1116
1117         for (i=0; i<num_ab; ++i) {
1118
1119                 if ((i / NAMESPERPAGE) != page) {       /* New tab */
1120                         page = (i / NAMESPERPAGE);
1121                         if (page > 0) {
1122                                 wc_printf("</tr></table>\n");
1123                                 end_tab(page-1, num_pages);
1124                         }
1125                         begin_tab(page, num_pages);
1126                         wc_printf("<table border=\"0\" cellspacing=\"0\" cellpadding=\"3\" width=\"100%%\">\n");
1127                         displayed = 0;
1128                 }
1129
1130                 if ((displayed % 4) == 0) {
1131                         if (displayed > 0) {
1132                                 wc_printf("</tr>\n");
1133                         }
1134                         bg = 1 - bg;
1135                         wc_printf("<tr bgcolor=\"#%s\">",
1136                                 (bg ? "dddddd" : "ffffff")
1137                         );
1138                 }
1139         
1140                 wc_printf("<td>");
1141
1142                 wc_printf("<a href=\"readfwd?startmsg=%ld?is_singlecard=1",
1143                         addrbook[i].ab_msgnum);
1144                 wc_printf("?maxmsgs=1?is_summary=0?alpha=%s\">", bstr("alpha"));
1145                 vcard_n_prettyize(addrbook[i].ab_name);
1146                 escputs(addrbook[i].ab_name);
1147                 wc_printf("</a></td>\n");
1148                 ++displayed;
1149         }
1150
1151         /* Placeholders for empty columns at end */
1152         if ((num_ab % 4) != 0) {
1153                 for (i=0; i<(4-(num_ab % 4)); ++i) {
1154                         wc_printf("<td>&nbsp;</td>");
1155                 }
1156         }
1157
1158         wc_printf("</tr></table>\n");
1159         end_tab((num_pages-1), num_pages);
1160
1161         begin_tab(num_pages, num_pages);
1162         /* FIXME there ought to be something here */
1163         end_tab(num_pages, num_pages);
1164
1165         for (i=0; i<num_pages; ++i) {
1166                 free(tablabels[i]);
1167         }
1168         free(tablabels);
1169 }
1170
1171
1172
1173
1174 /*
1175  * Edit the vCard component of a MIME message.  
1176  * Supply the message number
1177  * and MIME part number to fetch.  Or, specify -1 for the message number
1178  * to start with a blank card.
1179  */
1180 void do_edit_vcard(long msgnum, char *partnum, 
1181                    message_summary *VCMsg,
1182                    wc_mime_attachment *VCAtt,
1183                    const char *return_to, 
1184                    const char *force_room) {
1185         wcsession *WCC = WC;
1186         message_summary *Msg = NULL;
1187         wc_mime_attachment *VCMime = NULL;
1188         struct vCard *v;
1189         int i;
1190         char *key, *value;
1191         char whatuser[256];
1192
1193         char lastname[256];
1194         char firstname[256];
1195         char middlename[256];
1196         char prefix[256];
1197         char suffix[256];
1198         char pobox[256];
1199         char extadr[256];
1200         char street[256];
1201         char city[256];
1202         char state[256];
1203         char zipcode[256];
1204         char country[256];
1205         char hometel[256];
1206         char worktel[256];
1207         char faxtel[256];
1208         char mobiletel[256];
1209         char primary_inetemail[256];
1210         char other_inetemail[SIZ];
1211         char extrafields[SIZ];
1212         char fullname[256];
1213         char title[256];
1214         char org[256];
1215
1216         lastname[0] = 0;
1217         firstname[0] = 0;
1218         middlename[0] = 0;
1219         prefix[0] = 0;
1220         suffix[0] = 0;
1221         pobox[0] = 0;
1222         extadr[0] = 0;
1223         street[0] = 0;
1224         city[0] = 0;
1225         state[0] = 0;
1226         zipcode[0] = 0;
1227         country[0] = 0;
1228         hometel[0] = 0;
1229         worktel[0] = 0;
1230         faxtel[0] = 0;
1231         mobiletel[0] = 0;
1232         primary_inetemail[0] = 0;
1233         other_inetemail[0] = 0;
1234         title[0] = 0;
1235         org[0] = 0;
1236         extrafields[0] = 0;
1237         fullname[0] = 0;
1238
1239         safestrncpy(whatuser, "", sizeof whatuser);
1240
1241         if ((msgnum >= 0) || 
1242             ((VCMsg != NULL) && (VCAtt != NULL)))
1243         {
1244                 if ((VCMsg == NULL) && (VCAtt == NULL)) {
1245
1246                         Msg = (message_summary *) malloc(sizeof(message_summary));
1247                         memset(Msg, 0, sizeof(message_summary));
1248                         Msg->msgnum = msgnum;
1249                         VCMime = load_vcard(Msg);
1250                         if (VCMime == NULL) {
1251                                 convenience_page("770000", _("Error"), "");///TODO: important message
1252                                 DestroyMessageSummary(Msg);
1253                                 return;
1254                         }
1255                 
1256                         v = VCardLoad(VCMime->Data);
1257                 }
1258                 else {
1259                         v = VCardLoad(VCAtt->Data);
1260                 }
1261         
1262                 /* Populate the variables for our form */
1263                 i = 0;
1264                 while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
1265                         char prp[256];  /* property name */
1266                         char prm[256];  /* parameters */
1267
1268                         value = vcard_get_prop(v, "", 0, i++, 0);
1269
1270
1271                         extract_token(prp, key, 0, ';', sizeof prp);
1272                         safestrncpy(prm, key, sizeof prm);
1273                         remove_token(prm, 0, ';');
1274
1275                         if (!strcasecmp(prp, "n")) {
1276                                 extract_token(lastname, value, 0, ';', sizeof lastname);
1277                                 extract_token(firstname, value, 1, ';', sizeof firstname);
1278                                 extract_token(middlename, value, 2, ';', sizeof middlename);
1279                                 extract_token(prefix, value, 3, ';', sizeof prefix);
1280                                 extract_token(suffix, value, 4, ';', sizeof suffix);
1281                         }
1282
1283                         else if (!strcasecmp(prp, "fn")) {
1284                                 safestrncpy(fullname, value, sizeof fullname);
1285                         }
1286
1287                         else if (!strcasecmp(prp, "title")) {
1288                                 safestrncpy(title, value, sizeof title);
1289                         }
1290         
1291                         else if (!strcasecmp(prp, "org")) {
1292                                 safestrncpy(org, value, sizeof org);
1293                         }
1294         
1295                         else if (!strcasecmp(prp, "adr")) {
1296                                 extract_token(pobox, value, 0, ';', sizeof pobox);
1297                                 extract_token(extadr, value, 1, ';', sizeof extadr);
1298                                 extract_token(street, value, 2, ';', sizeof street);
1299                                 extract_token(city, value, 3, ';', sizeof city);
1300                                 extract_token(state, value, 4, ';', sizeof state);
1301                                 extract_token(zipcode, value, 5, ';', sizeof zipcode);
1302                                 extract_token(country, value, 6, ';', sizeof country);
1303                         }
1304
1305                         else if (!strcasecmp(prp, "tel")) {
1306
1307                                 if (bmstrcasestr(prm, "home")) {
1308                                         extract_token(hometel, value, 0, ';', sizeof hometel);
1309                                 }
1310                                 else if (bmstrcasestr(prm, "work")) {
1311                                         extract_token(worktel, value, 0, ';', sizeof worktel);
1312                                 }
1313                                 else if (bmstrcasestr(prm, "fax")) {
1314                                         extract_token(faxtel, value, 0, ';', sizeof faxtel);
1315                                 }
1316                                 else if (bmstrcasestr(prm, "cell")) {
1317                                         extract_token(mobiletel, value, 0, ';', sizeof mobiletel);
1318                                 }
1319                                 else {  /* Missing or unknown type; put it in the home phone */
1320                                         extract_token(hometel, value, 0, ';', sizeof hometel);
1321                                 }
1322                         }
1323         
1324                         else if ( (!strcasecmp(prp, "email")) && (bmstrcasestr(prm, "internet")) ) {
1325                                 if (primary_inetemail[0] == 0) {
1326                                         safestrncpy(primary_inetemail, value, sizeof primary_inetemail);
1327                                 }
1328                                 else {
1329                                         if (other_inetemail[0] != 0) {
1330                                                 strcat(other_inetemail, "\n");
1331                                         }
1332                                         strcat(other_inetemail, value);
1333                                 }
1334                         }
1335
1336                         /* Unrecognized properties are preserved here so we don't discard them
1337                          * just because the vCard was edited with WebCit.
1338                          */
1339                         else {
1340                                 strcat(extrafields, key);
1341                                 strcat(extrafields, ":");
1342                                 strcat(extrafields, value);
1343                                 strcat(extrafields, "\n");
1344                         }
1345         
1346                 }
1347         
1348                 vcard_free(v);
1349         }
1350
1351         /* Display the form */
1352         output_headers(1, 1, 1, 0, 0, 0);
1353
1354         do_template("box_begin_1");
1355         StrBufAppendBufPlain(WC->WBuf, _("Edit contact information"), -1, 0);
1356         do_template("box_begin_2");
1357
1358         wc_printf("<form method=\"POST\" action=\"submit_vcard\">\n");
1359         wc_printf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WC->nonce);
1360
1361         if (force_room != NULL) {
1362                 wc_printf("<input type=\"hidden\" name=\"force_room\" value=\"");
1363                 escputs(force_room);
1364                 wc_printf("\">\n");
1365         }
1366         else
1367         {
1368                 wc_printf("<input type=\"hidden\" name=\"go\" value=\"");
1369                 StrEscAppend(WCC->WBuf, WCC->CurRoom.name, NULL, 0, 0);
1370                 wc_printf("\">\n");
1371         }
1372
1373         wc_printf("<table class=\"vcard_edit_background\"><tr><td>\n");
1374
1375         wc_printf("<table border=\"0\"><tr>"
1376                 "<td>%s</td>"
1377                 "<td>%s</td>"
1378                 "<td>%s</td>"
1379                 "<td>%s</td>"
1380                 "<td>%s</td></tr>\n",
1381                 _("Prefix"), _("First Name"), _("Middle Name"), _("Last Name"), _("Suffix")
1382         );
1383         wc_printf("<tr><td><input type=\"text\" name=\"prefix\" "
1384                 "value=\"%s\" maxlength=\"5\" size=\"5\"></td>",
1385                 prefix);
1386         wc_printf("<td><input type=\"text\" name=\"firstname\" "
1387                 "value=\"%s\" maxlength=\"29\"></td>",
1388                 firstname);
1389         wc_printf("<td><input type=\"text\" name=\"middlename\" "
1390                 "value=\"%s\" maxlength=\"29\"></td>",
1391                 middlename);
1392         wc_printf("<td><input type=\"text\" name=\"lastname\" "
1393                 "value=\"%s\" maxlength=\"29\"></td>",
1394                 lastname);
1395         wc_printf("<td><input type=\"text\" name=\"suffix\" "
1396                 "value=\"%s\" maxlength=\"10\" size=\"10\"></td></tr></table>\n",
1397                 suffix);
1398
1399         wc_printf("<table  class=\"vcard_edit_background_alt\">");
1400         wc_printf("<tr><td>");
1401
1402         wc_printf(_("Display name:"));
1403         wc_printf("<br>"
1404                 "<input type=\"text\" name=\"fullname\" "
1405                 "value=\"%s\" maxlength=\"40\"><br><br>\n",
1406                 fullname
1407         );
1408
1409         wc_printf(_("Title:"));
1410         wc_printf("<br>"
1411                 "<input type=\"text\" name=\"title\" "
1412                 "value=\"%s\" maxlength=\"40\"><br><br>\n",
1413                 title
1414         );
1415
1416         wc_printf(_("Organization:"));
1417         wc_printf("<br>"
1418                 "<input type=\"text\" name=\"org\" "
1419                 "value=\"%s\" maxlength=\"40\"><br><br>\n",
1420                 org
1421         );
1422
1423         wc_printf("</td><td>");
1424
1425         wc_printf("<table border=\"0\">");
1426         wc_printf("<tr><td>");
1427         wc_printf(_("PO box:"));
1428         wc_printf("</td><td>"
1429                 "<input type=\"text\" name=\"pobox\" "
1430                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
1431                 pobox);
1432         wc_printf("<tr><td>");
1433         wc_printf(_("Address:"));
1434         wc_printf("</td><td>"
1435                 "<input type=\"text\" name=\"extadr\" "
1436                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
1437                 extadr);
1438         wc_printf("<tr><td> </td><td>"
1439                 "<input type=\"text\" name=\"street\" "
1440                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
1441                 street);
1442         wc_printf("<tr><td>");
1443         wc_printf(_("City:"));
1444         wc_printf("</td><td>"
1445                 "<input type=\"text\" name=\"city\" "
1446                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
1447                 city);
1448         wc_printf("<tr><td>");
1449         wc_printf(_("State:"));
1450         wc_printf("</td><td>"
1451                 "<input type=\"text\" name=\"state\" "
1452                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
1453                 state);
1454         wc_printf("<tr><td>");
1455         wc_printf(_("ZIP code:"));
1456         wc_printf("</td><td>"
1457                 "<input type=\"text\" name=\"zipcode\" "
1458                 "value=\"%s\" maxlength=\"10\"></td></tr>\n",
1459                 zipcode);
1460         wc_printf("<tr><td>");
1461         wc_printf(_("Country:"));
1462         wc_printf("</td><td>"
1463                 "<input type=\"text\" name=\"country\" "
1464                 "value=\"%s\" maxlength=\"29\" width=\"5\"></td></tr>\n",
1465                 country);
1466         wc_printf("</table>\n");
1467
1468         wc_printf("</table>\n");
1469
1470         wc_printf("<table border=0><tr><td>");
1471         wc_printf(_("Home telephone:"));
1472         wc_printf("</td>"
1473                 "<td><input type=\"text\" name=\"hometel\" "
1474                 "value=\"%s\" maxlength=\"29\"></td>\n",
1475                 hometel);
1476         wc_printf("<td>");
1477         wc_printf(_("Work telephone:"));
1478         wc_printf("</td>"
1479                 "<td><input type=\"text\" name=\"worktel\" "
1480                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
1481                 worktel);
1482         wc_printf("<tr><td>");
1483         wc_printf(_("Mobile telephone:"));
1484         wc_printf("</td>"
1485                 "<td><input type=\"text\" name=\"mobiletel\" "
1486                 "value=\"%s\" maxlength=\"29\"></td>\n",
1487                 mobiletel);
1488         wc_printf("<td>");
1489         wc_printf(_("Fax number:"));
1490         wc_printf("</td>"
1491                 "<td><input type=\"text\" name=\"faxtel\" "
1492                 "value=\"%s\" maxlength=\"29\"></td></tr></table>\n",
1493                 faxtel);
1494
1495         wc_printf("<table class=\"vcard_edit_background_alt\">");
1496         wc_printf("<tr><td>");
1497
1498         wc_printf("<table border=0><TR>"
1499                 "<td valign=top>");
1500         wc_printf(_("Primary Internet e-mail address"));
1501         wc_printf("<br>"
1502                 "<input type=\"text\" name=\"primary_inetemail\" "
1503                 "size=40 maxlength=60 value=\"");
1504         escputs(primary_inetemail);
1505         wc_printf("\"><br>"
1506                 "</td><td valign=top>");
1507         wc_printf(_("Internet e-mail aliases"));
1508         wc_printf("<br>"
1509                 "<textarea name=\"other_inetemail\" rows=5 cols=40 width=40>");
1510         escputs(other_inetemail);
1511         wc_printf("</textarea></td></tr></table>\n");
1512
1513         wc_printf("</td></tr></table>\n");
1514
1515         wc_printf("<input type=\"hidden\" name=\"extrafields\" value=\"");
1516         escputs(extrafields);
1517         wc_printf("\">\n");
1518
1519         wc_printf("<input type=\"hidden\" name=\"return_to\" value=\"");
1520         escputs(return_to);
1521         wc_printf("\">\n");
1522
1523         wc_printf("<div class=\"buttons\">\n"
1524                 "<input type=\"submit\" name=\"ok_button\" value=\"%s\">"
1525                 "&nbsp;"
1526                 "<input type=\"submit\" name=\"cancel_button\" value=\"%s\">"
1527                 "</div></form>\n",
1528                 _("Save changes"),
1529                 _("Cancel")
1530         );
1531         
1532         wc_printf("</td></tr></table>\n");
1533         do_template("box_end");
1534         wDumpContent(1);
1535         if (Msg != NULL) {
1536                 DestroyMessageSummary(Msg);
1537         }
1538 }
1539
1540
1541 /*
1542  *  commit the edits to the citadel server
1543  */
1544 void edit_vcard(void) {
1545         long msgnum;
1546         char *partnum;
1547
1548         msgnum = lbstr("msgnum");
1549         partnum = bstr("partnum");
1550         do_edit_vcard(msgnum, partnum, NULL, NULL, "", NULL);
1551 }
1552
1553
1554
1555 /*
1556  *  parse edited vcard from the browser
1557  */
1558 void submit_vcard(void) {
1559         struct vCard *v;
1560         char *serialized_vcard;
1561         char buf[SIZ];
1562         StrBuf *Buf;
1563         const StrBuf *ForceRoom;
1564         int i;
1565
1566         if (!havebstr("ok_button")) { 
1567                 readloop(readnew, eUseDefault);
1568                 return;
1569         }
1570
1571         if (havebstr("force_room")) {
1572                 ForceRoom = sbstr("force_room");
1573                 if (gotoroom(ForceRoom) != 200) {
1574                         AppendImportantMessage(_("Unable to enter the room to save your message"), -1);
1575                         AppendImportantMessage(HKEY(": "));
1576                         AppendImportantMessage(SKEY(ForceRoom));
1577                         AppendImportantMessage(HKEY("; "));
1578                         AppendImportantMessage(_("Aborting."), -1);
1579
1580                         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
1581                                 select_user_to_edit(NULL);
1582                         }
1583                         else if (!strcmp(bstr("return_to"), "do_welcome")) {
1584                                 do_welcome();
1585                         }
1586                         else if (!IsEmptyStr(bstr("return_to"))) {
1587                                 http_redirect(bstr("return_to"));
1588                         }
1589                         else {
1590                                 readloop(readnew, eUseDefault);
1591                         }
1592                         return;
1593                 }
1594         }
1595
1596         Buf = NewStrBuf();
1597         serv_write(HKEY("ENT0 1|||4\n"));
1598         if (!StrBuf_ServGetln(Buf) && (GetServerStatus(Buf, NULL) != 4))
1599         {
1600                 edit_vcard();
1601                 return;
1602         }
1603
1604         /* Make a vCard structure out of the data supplied in the form */
1605         StrBufPrintf(Buf, "begin:vcard\r\n%s\r\nend:vcard\r\n",
1606                      bstr("extrafields")
1607         );
1608         v = VCardLoad(Buf);     /* Start with the extra fields */
1609         if (v == NULL) {
1610                 AppendImportantMessage(_("An error has occurred."), -1);
1611                 edit_vcard();
1612                 FreeStrBuf(&Buf);
1613                 return;
1614         }
1615
1616         snprintf(buf, sizeof buf, "%s;%s;%s;%s;%s",
1617                 bstr("lastname"),
1618                 bstr("firstname"),
1619                 bstr("middlename"),
1620                 bstr("prefix"),
1621                 bstr("suffix") );
1622         vcard_add_prop(v, "n", buf);
1623         
1624         vcard_add_prop(v, "title", bstr("title"));
1625         vcard_add_prop(v, "fn", bstr("fullname"));
1626         vcard_add_prop(v, "org", bstr("org"));
1627
1628         snprintf(buf, sizeof buf, "%s;%s;%s;%s;%s;%s;%s",
1629                 bstr("pobox"),
1630                 bstr("extadr"),
1631                 bstr("street"),
1632                 bstr("city"),
1633                 bstr("state"),
1634                 bstr("zipcode"),
1635                 bstr("country") );
1636         vcard_add_prop(v, "adr", buf);
1637
1638         vcard_add_prop(v, "tel;home", bstr("hometel"));
1639         vcard_add_prop(v, "tel;work", bstr("worktel"));
1640         vcard_add_prop(v, "tel;fax", bstr("faxtel"));
1641         vcard_add_prop(v, "tel;cell", bstr("mobiletel"));
1642         vcard_add_prop(v, "email;internet", bstr("primary_inetemail"));
1643
1644         for (i=0; i<num_tokens(bstr("other_inetemail"), '\n'); ++i) {
1645                 extract_token(buf, bstr("other_inetemail"), i, '\n', sizeof buf);
1646                 if (!IsEmptyStr(buf)) {
1647                         vcard_add_prop(v, "email;internet", buf);
1648                 }
1649         }
1650
1651         serialized_vcard = vcard_serialize(v);
1652         vcard_free(v);
1653         if (serialized_vcard == NULL) {
1654                 AppendImportantMessage(_("An error has occurred."), -1);
1655                 edit_vcard();
1656                 FreeStrBuf(&Buf);
1657                 return;
1658         }
1659
1660         serv_write(HKEY("Content-type: text/x-vcard; charset=UTF-8\n"));
1661         serv_write(HKEY("\n"));
1662         serv_printf("%s\r\n", serialized_vcard);
1663         serv_write(HKEY("000\n"));
1664         free(serialized_vcard);
1665
1666         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
1667                 select_user_to_edit(NULL);
1668         }
1669         else if (!strcmp(bstr("return_to"), "do_welcome")) {
1670                 do_welcome();
1671         }
1672         else if (!IsEmptyStr(bstr("return_to"))) {
1673                 http_redirect(bstr("return_to"));
1674         }
1675         else {
1676                 readloop(readnew, eUseDefault);
1677         }
1678         FreeStrBuf(&Buf);
1679 }
1680
1681
1682
1683 /*
1684  * Extract an embedded photo from a vCard for display on the client
1685  */
1686 void display_vcard_photo_img(void)
1687 {
1688         long msgnum = 0L;
1689         StrBuf *vcard;
1690         struct vCard *v;
1691         char *photosrc;
1692         const char *contentType;
1693         wcsession *WCC = WC;
1694
1695         msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
1696         
1697         vcard = load_mimepart(msgnum,"1");
1698         v = VCardLoad(vcard);
1699         
1700         photosrc = vcard_get_prop(v, "PHOTO", 1,0,0);
1701         FlushStrBuf(WCC->WBuf);
1702         StrBufAppendBufPlain(WCC->WBuf, photosrc, -1, 0);
1703         if (StrBufDecodeBase64(WCC->WBuf) <= 0) {
1704                 FlushStrBuf(WCC->WBuf);
1705                 
1706                 hprintf("HTTP/1.1 500 %s\n","Unable to get photo");
1707                 output_headers(0, 0, 0, 0, 0, 0);
1708                 hprintf("Content-Type: text/plain\r\n");
1709                 begin_burst();
1710                 wc_printf(_("Could Not decode vcard photo\n"));
1711                 end_burst();
1712                 return;
1713         }
1714         contentType = GuessMimeType(ChrPtr(WCC->WBuf), StrLength(WCC->WBuf));
1715         http_transmit_thing(contentType, 0);
1716         free(v);
1717         free(photosrc);
1718 }
1719
1720 typedef struct _vcardview_struct {
1721         long is_singlecard;
1722         addrbookent *addrbook;
1723         long num_ab;
1724
1725 } vcardview_struct;
1726
1727 int vcard_GetParamsGetServerCall(SharedMessageStatus *Stat, 
1728                                  void **ViewSpecific, 
1729                                  long oper, 
1730                                  char *cmd, 
1731                                  long len,
1732                                  char *filter,
1733                                  long flen)
1734 {
1735         vcardview_struct *VS;
1736
1737         VS = (vcardview_struct*) malloc (sizeof(vcardview_struct));
1738         memset(VS, 0, sizeof(vcardview_struct));
1739         *ViewSpecific = (void*)VS;
1740
1741         VS->is_singlecard = ibstr("is_singlecard");
1742         if (VS->is_singlecard != 1) {
1743                 if (oper == do_search) {
1744                         snprintf(cmd, len, "MSGS SEARCH|%s", bstr("query"));
1745                 }
1746                 else {
1747                         strcpy(cmd, "MSGS ALL");
1748                 }
1749                 Stat->maxmsgs = 9999999;
1750         }
1751         return 200;
1752 }
1753
1754 int vcard_LoadMsgFromServer(SharedMessageStatus *Stat, 
1755                             void **ViewSpecific, 
1756                             message_summary* Msg, 
1757                             int is_new, 
1758                             int i)
1759 {
1760         vcardview_struct *VS;
1761         char *ab_name;
1762
1763         VS = (vcardview_struct*) *ViewSpecific;
1764
1765         ab_name = NULL;
1766         fetch_ab_name(Msg, &ab_name);
1767         if (ab_name == NULL) 
1768                 return 0;
1769         ++VS->num_ab;
1770         VS->addrbook = realloc(VS->addrbook,
1771                                (sizeof(addrbookent) * VS->num_ab) );
1772         safestrncpy(VS->addrbook[VS->num_ab-1].ab_name, ab_name,
1773                     sizeof(VS->addrbook[VS->num_ab-1].ab_name));
1774         VS->addrbook[VS->num_ab-1].ab_msgnum = Msg->msgnum;
1775         free(ab_name);
1776         return 0;
1777 }
1778
1779
1780 int vcard_RenderView_or_Tail(SharedMessageStatus *Stat, void **ViewSpecific, long oper)
1781 {
1782         const StrBuf *Mime;
1783         vcardview_struct *VS;
1784
1785         VS = (vcardview_struct*) *ViewSpecific;
1786         if (VS->is_singlecard)
1787                 read_message(WC->WBuf, HKEY("view_message"), lbstr("startmsg"), NULL, &Mime);
1788         else
1789                 do_addrbook_view(VS->addrbook, VS->num_ab);     /* Render the address book */
1790         return 0;
1791 }
1792
1793 int vcard_Cleanup(void **ViewSpecific)
1794 {
1795         vcardview_struct *VS;
1796
1797         VS = (vcardview_struct*) *ViewSpecific;
1798         wDumpContent(1);
1799         if ((VS != NULL) && 
1800             (VS->addrbook != NULL))
1801                 free(VS->addrbook);
1802         if (VS != NULL) 
1803                 free(VS);
1804         return 0;
1805 }
1806
1807 void 
1808 ServerStartModule_VCARD
1809 (void)
1810 {
1811         ///VCToEnum = NewHash(0, NULL);
1812
1813 }
1814
1815 void 
1816 ServerShutdownModule_VCARD
1817 (void)
1818 {
1819         /// DeleteHash(&VCToEnum);
1820 }
1821
1822 void 
1823 InitModule_VCARD
1824 (void)
1825 {
1826         RegisterCTX(CTX_VCARD);
1827         RegisterReadLoopHandlerset(
1828                 VIEW_ADDRESSBOOK,
1829                 vcard_GetParamsGetServerCall,
1830                 NULL,
1831                 NULL,
1832                 NULL, 
1833                 vcard_LoadMsgFromServer,
1834                 vcard_RenderView_or_Tail,
1835                 vcard_Cleanup);
1836         WebcitAddUrlHandler(HKEY("edit_vcard"), "", 0, edit_vcard, 0);
1837         WebcitAddUrlHandler(HKEY("submit_vcard"), "", 0, submit_vcard, 0);
1838         WebcitAddUrlHandler(HKEY("vcardphoto"), "", 0, display_vcard_photo_img, NEED_URL);
1839 /*
1840         Put(VCToEnum, HKEY("n"), (void*)VC_n, reference_free_handler);
1841         Put(VCToEnum, HKEY("fn"), (void*)VC_fn, reference_free_handler);
1842         Put(VCToEnum, HKEY("title"), (void*)VC_title, reference_free_handler);
1843         Put(VCToEnum, HKEY("org"), (void*)VC_org, reference_free_handler);
1844         Put(VCToEnum, HKEY("email"), (void*)VC_email, reference_free_handler);
1845         Put(VCToEnum, HKEY("tel"), (void*)VC_tel, reference_free_handler);
1846         Put(VCToEnum, HKEY("work"), (void*)VC_work, reference_free_handler);
1847         Put(VCToEnum, HKEY("home"), (void*)VC_home, reference_free_handler);
1848         Put(VCToEnum, HKEY("cell"), (void*)VC_cell, reference_free_handler);
1849         Put(VCToEnum, HKEY("adr"), (void*)VC_adr, reference_free_handler);
1850         Put(VCToEnum, HKEY("photo"), (void*)VC_photo, reference_free_handler);
1851         Put(VCToEnum, HKEY("version"), (void*)VC_version, reference_free_handler);
1852         Put(VCToEnum, HKEY("rev"), (void*)VC_rev, reference_free_handler);
1853         Put(VCToEnum, HKEY("label"), (void*)VC_label, reference_free_handler);
1854 */
1855 /*
1856         RegisterNamespace("VC", 1, 2, tmplput_VCARD_ITEM, NULL, CTX_VCARD);
1857
1858         REGISTERTokenParamDefine(VC_n);
1859         REGISTERTokenParamDefine(VC_fn);
1860         REGISTERTokenParamDefine(VC_title);
1861         REGISTERTokenParamDefine(VC_org);
1862         REGISTERTokenParamDefine(VC_email);
1863         REGISTERTokenParamDefine(VC_tel);
1864         REGISTERTokenParamDefine(VC_work);
1865         REGISTERTokenParamDefine(VC_home);
1866         REGISTERTokenParamDefine(VC_cell);
1867         REGISTERTokenParamDefine(VC_adr);
1868         REGISTERTokenParamDefine(VC_photo);
1869         REGISTERTokenParamDefine(VC_version);
1870         REGISTERTokenParamDefine(VC_rev);
1871         REGISTERTokenParamDefine(VC_label);
1872 */
1873
1874         {
1875                 StrBuf *Prefix  = NewStrBufPlain(HKEY("VC:"));
1876                 DefineToToken   = NewHash(1, lFlathash);
1877                 VCTokenToDefine = NewHash(1, NULL);
1878                 autoRegisterTokens(&VCEnumCounter, VCStrE, Prefix, 0);
1879                 FreeStrBuf(&Prefix);
1880         }
1881         RegisterCTX(CTX_VCARD);
1882         RegisterNamespace("VC:ITEM", 2, 2, tmpl_vcard_item, preeval_vcard_item, CTX_VCARD);
1883         RegisterNamespace("VC:NAME", 1, 1, tmpl_vcard_name_str, preeval_vcard_name_str, CTX_VCARD);
1884         REGISTERTokenParamDefine(FlatString);
1885         REGISTERTokenParamDefine(StringCluster);
1886         REGISTERTokenParamDefine(PhoneNumber);
1887         REGISTERTokenParamDefine(EmailAddr);
1888         REGISTERTokenParamDefine(Street);
1889         REGISTERTokenParamDefine(Number);
1890         REGISTERTokenParamDefine(AliasFor);
1891         REGISTERTokenParamDefine(Base64BinaryAttachment);
1892         REGISTERTokenParamDefine(TerminateList);
1893
1894         RegisterConditional("VC:HAVE:TYPE",      1, conditional_VC_Havetype, CTX_VCARD);
1895         RegisterFilteredIterator("VC:TYPE", 1, NULL, NULL, NULL, NULL, filter_VC_ByType, CTX_STRBUF, CTX_VCARD, IT_NOFLAG);
1896 }
1897