* add a Display name to our handlers; this will be used by DAV handlers.
[citadel.git] / webcit / vcard_edit.c
1 /*
2  * $Id$
3  */
4
5 #include "webcit.h"
6 #include "calendar.h"
7
8 /*
9  * Record compare function for sorting address book indices
10  */
11 int abcmp(const void *ab1, const void *ab2) {
12         return(strcasecmp(
13                 (((const addrbookent *)ab1)->ab_name),
14                 (((const addrbookent *)ab2)->ab_name)
15         ));
16 }
17
18
19 /*
20  * Helper function for do_addrbook_view()
21  * Converts a name into a three-letter tab label
22  */
23 void nametab(char *tabbuf, long len, char *name) {
24         stresc(tabbuf, len, name, 0, 0);
25         tabbuf[0] = toupper(tabbuf[0]);
26         tabbuf[1] = tolower(tabbuf[1]);
27         tabbuf[2] = tolower(tabbuf[2]);
28         tabbuf[3] = 0;
29 }
30
31
32 /*
33  * display the adressbook overview
34  */
35 void display_addressbook(long msgnum, char alpha) {
36         //char buf[SIZ];
37         /* char mime_partnum[SIZ]; */
38         ///char mime_disposition[SIZ];
39         //int mime_length;
40         char vcard_partnum[SIZ];
41         StrBuf *vcard_source = NULL;
42         message_summary summ;////TODO: this will leak
43
44         memset(&summ, 0, sizeof(summ));
45         // safestrncpy(summ.subj, _("(no subject)"), sizeof summ.subj);
46         // Load Message headers
47         if (!IsEmptyStr(vcard_partnum)) {
48                 vcard_source = load_mimepart(msgnum, vcard_partnum);
49                 if (vcard_source != NULL) {
50
51                         /* Display the summary line */
52                         display_vcard(WC->WBuf, vcard_source, alpha, 0, NULL, msgnum);
53
54                         /* If it's my vCard I can edit it */
55                         if (    (!strcasecmp(ChrPtr(WC->wc_roomname), USERCONFIGROOM))
56                                 || (!strcasecmp(&(ChrPtr(WC->wc_roomname)[11]), USERCONFIGROOM))
57                                 || (WC->wc_view == VIEW_ADDRESSBOOK)
58                         ) {
59                                 wprintf("<a href=\"edit_vcard?"
60                                         "msgnum=%ld&partnum=%s\">",
61                                         msgnum, vcard_partnum);
62                                 wprintf("[%s]</a>", _("edit"));
63                         }
64
65                         FreeStrBuf(&vcard_source);
66                 }
67         }
68
69 }
70
71
72
73 /*
74  * If it's an old "Firstname Lastname" style record, try to convert it.
75  */
76 void lastfirst_firstlast(char *namebuf) {
77         char firstname[SIZ];
78         char lastname[SIZ];
79         int i;
80
81         if (namebuf == NULL) return;
82         if (strchr(namebuf, ';') != NULL) return;
83
84         i = num_tokens(namebuf, ' ');
85         if (i < 2) return;
86
87         extract_token(lastname, namebuf, i-1, ' ', sizeof lastname);
88         remove_token(namebuf, i-1, ' ');
89         strcpy(firstname, namebuf);
90         sprintf(namebuf, "%s; %s", lastname, firstname);
91 }
92
93
94
95 wc_mime_attachment *load_vcard(message_summary *Msg) 
96 {
97         HashPos  *it;
98         StrBuf *FoundCharset = NewStrBuf();
99         StrBuf *Error;
100         void *vMime;
101         const char *Key;
102         long len;
103         wc_mime_attachment *Mime;
104         wc_mime_attachment *VCMime = NULL;
105
106         Msg->MsgBody =  (wc_mime_attachment*) malloc(sizeof(wc_mime_attachment));
107         memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
108         Msg->MsgBody->msgnum = Msg->msgnum;
109
110         load_message(Msg, FoundCharset, &Error);
111
112         FreeStrBuf(&FoundCharset);
113         /* look up the vcard... */
114         it = GetNewHashPos(Msg->AllAttach, 0);
115         while (GetNextHashPos(Msg->AllAttach, it, &len, &Key, &vMime) && 
116                (vMime != NULL)) 
117         {
118                 Mime = (wc_mime_attachment*) vMime;
119                 if ((strcmp(ChrPtr(Mime->ContentType),
120                            "text/x-vcard") == 0) ||
121                     (strcmp(ChrPtr(Mime->ContentType),
122                             "text/vcard") == 0))
123                 {
124                         VCMime = Mime;
125                         break;
126                 }
127         }
128         DeleteHashPos(&it);
129         if (VCMime == NULL)
130                 return NULL;
131
132         MimeLoadData(VCMime);
133         return VCMime;
134 }
135
136 /*
137  * fetch the display name off a vCard
138  */
139 void fetch_ab_name(message_summary *Msg, char **namebuf) {
140         long len;
141         int i;
142         wc_mime_attachment *VCMime = NULL;
143
144         if (namebuf == NULL) return;
145
146         VCMime = load_vcard(Msg);
147         if (VCMime == NULL)
148                 return;
149
150         /* Grab the name off the card */
151         display_vcard(WC->WBuf, VCMime->Data, 0, 0, namebuf, Msg->msgnum);
152
153         if (*namebuf != NULL) {
154                 lastfirst_firstlast(*namebuf);
155                 striplt(*namebuf);
156                 len = strlen(*namebuf);
157                 for (i=0; i<len; ++i) {
158                         if ((*namebuf)[i] != ';') return;
159                 }
160                 free (*namebuf);
161                 (*namebuf) = strdup(_("(no name)"));
162         }
163         else {
164                 (*namebuf) = strdup(_("(no name)"));
165         }
166 }
167
168
169
170 /*
171  * Turn a vCard "n" (name) field into something displayable.
172  */
173 void vcard_n_prettyize(char *name)
174 {
175         char *original_name;
176         int i, j, len;
177
178         original_name = strdup(name);
179         len = strlen(original_name);
180         for (i=0; i<5; ++i) {
181                 if (len > 0) {
182                         if (original_name[len-1] == ' ') {
183                                 original_name[--len] = 0;
184                         }
185                         if (original_name[len-1] == ';') {
186                                 original_name[--len] = 0;
187                         }
188                 }
189         }
190         strcpy(name, "");
191         j=0;
192         for (i=0; i<len; ++i) {
193                 if (original_name[i] == ';') {
194                         name[j++] = ',';
195                         name[j++] = ' ';                        
196                 }
197                 else {
198                         name[j++] = original_name[i];
199                 }
200         }
201         name[j] = '\0';
202         free(original_name);
203 }
204
205
206
207
208 /*
209  * preparse a vcard name
210  * display_vcard() calls this after parsing the textual vCard into
211  * our 'struct vCard' data object.
212  * This gets called instead of display_parsed_vcard() if we are only looking
213  * to extract the person's name instead of displaying the card.
214  */
215 void fetchname_parsed_vcard(struct vCard *v, char **storename) {
216         char *name;
217         char *prop;
218         char buf[SIZ];
219         int j, n, len;
220         int is_qp = 0;
221         int is_b64 = 0;
222
223         *storename = NULL;
224
225         name = vcard_get_prop(v, "n", 1, 0, 0);
226         if (name != NULL) {
227                 len = strlen(name);
228                 prop = vcard_get_prop(v, "n", 1, 0, 1);
229                 n = num_tokens(prop, ';');
230
231                 for (j=0; j<n; ++j) {
232                         extract_token(buf, prop, j, ';', sizeof buf);
233                         if (!strcasecmp(buf, "encoding=quoted-printable")) {
234                                 is_qp = 1;
235                         }
236                         if (!strcasecmp(buf, "encoding=base64")) {
237                                 is_b64 = 1;
238                         }
239                 }
240                 if (is_qp) {
241                         // %ff can become 6 bytes in utf8 
242                         *storename = malloc(len * 2 + 3); 
243                         j = CtdlDecodeQuotedPrintable(
244                                 *storename, name,
245                                 len);
246                         (*storename)[j] = 0;
247                 }
248                 else if (is_b64) {
249                         // ff will become one byte..
250                         *storename = malloc(len + 50);
251                         CtdlDecodeBase64(
252                                 *storename, name,
253                                 len);
254                 }
255                 else {
256                         size_t len;
257
258                         len = strlen (name);
259                         
260                         *storename = malloc(len + 3); /* \0 + eventualy missing ', '*/
261                         memcpy(*storename, name, len + 1);
262                 }
263                 /* vcard_n_prettyize(storename); */
264         }
265
266 }
267
268
269
270 /*
271  * html print a vcard
272  * display_vcard() calls this after parsing the textual vCard into
273  * our 'struct vCard' data object.
274  *
275  * Set 'full' to nonzero to display the full card, otherwise it will only
276  * show a summary line.
277  *
278  * This code is a bit ugly, so perhaps an explanation is due: we do this
279  * in two passes through the vCard fields.  On the first pass, we process
280  * fields we understand, and then render them in a pretty fashion at the
281  * end.  Then we make a second pass, outputting all the fields we don't
282  * understand in a simple two-column name/value format.
283  * v            the vCard to display
284  * full         display all items of the vcard?
285  * msgnum       Citadel message pointer
286  */
287 void display_parsed_vcard(StrBuf *Target, struct vCard *v, int full, long msgnum) {
288         int i, j;
289         char buf[SIZ];
290         char *name;
291         int is_qp = 0;
292         int is_b64 = 0;
293         char *thisname, *thisvalue;
294         char firsttoken[SIZ];
295         int pass;
296
297         char fullname[SIZ];
298         char title[SIZ];
299         char org[SIZ];
300         char phone[SIZ];
301         char mailto[SIZ];
302
303         strcpy(fullname, "");
304         strcpy(phone, "");
305         strcpy(mailto, "");
306         strcpy(title, "");
307         strcpy(org, "");
308
309         if (!full) {
310                 StrBufAppendPrintf(Target, "<TD>");
311                 name = vcard_get_prop(v, "fn", 1, 0, 0);
312                 if (name != NULL) {
313                         StrEscAppend(Target, NULL, name, 0, 0);
314                 }
315                 else if (name = vcard_get_prop(v, "n", 1, 0, 0), name != NULL) {
316                         strcpy(fullname, name);
317                         vcard_n_prettyize(fullname);
318                         StrEscAppend(Target, NULL, fullname, 0, 0);
319                 }
320                 else {
321                         StrBufAppendPrintf(Target, "&nbsp;");
322                 }
323                 StrBufAppendPrintf(Target, "</TD>");
324                 return;
325         }
326
327         StrBufAppendPrintf(Target, "<div align=center>"
328                 "<table bgcolor=#aaaaaa width=50%%>");
329         for (pass=1; pass<=2; ++pass) {
330
331                 if (v->numprops) for (i=0; i<(v->numprops); ++i) {
332                         int len;
333                         thisname = strdup(v->prop[i].name);
334                         extract_token(firsttoken, thisname, 0, ';', sizeof firsttoken);
335         
336                         for (j=0; j<num_tokens(thisname, ';'); ++j) {
337                                 extract_token(buf, thisname, j, ';', sizeof buf);
338                                 if (!strcasecmp(buf, "encoding=quoted-printable")) {
339                                         is_qp = 1;
340                                         remove_token(thisname, j, ';');
341                                 }
342                                 if (!strcasecmp(buf, "encoding=base64")) {
343                                         is_b64 = 1;
344                                         remove_token(thisname, j, ';');
345                                 }
346                         }
347                         
348                         len = strlen(v->prop[i].value);
349                         /* if we have some untagged QP, detect it here. */
350                         if (!is_qp && (strstr(v->prop[i].value, "=?")!=NULL))
351                                 utf8ify_rfc822_string(&v->prop[i].value);
352
353                         if (is_qp) {
354                                 // %ff can become 6 bytes in utf8 
355                                 thisvalue = malloc(len * 2 + 3); 
356                                 j = CtdlDecodeQuotedPrintable(
357                                         thisvalue, v->prop[i].value,
358                                         len);
359                                 thisvalue[j] = 0;
360                         }
361                         else if (is_b64) {
362                                 // ff will become one byte..
363                                 thisvalue = malloc(len + 50);
364                                 CtdlDecodeBase64(
365                                         thisvalue, v->prop[i].value,
366                                         strlen(v->prop[i].value) );
367                         }
368                         else {
369                                 thisvalue = strdup(v->prop[i].value);
370                         }
371         
372                         /* Various fields we may encounter ***/
373         
374                         /* N is name, but only if there's no FN already there */
375                         if (!strcasecmp(firsttoken, "n")) {
376                                 if (IsEmptyStr(fullname)) {
377                                         strcpy(fullname, thisvalue);
378                                         vcard_n_prettyize(fullname);
379                                 }
380                         }
381         
382                         /* FN (full name) is a true 'display name' field */
383                         else if (!strcasecmp(firsttoken, "fn")) {
384                                 strcpy(fullname, thisvalue);
385                         }
386
387                         /* title */
388                         else if (!strcasecmp(firsttoken, "title")) {
389                                 strcpy(title, thisvalue);
390                         }
391         
392                         /* organization */
393                         else if (!strcasecmp(firsttoken, "org")) {
394                                 strcpy(org, thisvalue);
395                         }
396         
397                         else if (!strcasecmp(firsttoken, "email")) {
398                                 size_t len;
399                                 if (!IsEmptyStr(mailto)) strcat(mailto, "<br />");
400                                 strcat(mailto,
401                                         "<a href=\"display_enter"
402                                         "?force_room=_MAIL_?recp=");
403
404                                 len = strlen(mailto);
405                                 urlesc(&mailto[len], SIZ - len, "\"");
406                                 len = strlen(mailto);
407                                 urlesc(&mailto[len], SIZ - len,  fullname);
408                                 len = strlen(mailto);
409                                 urlesc(&mailto[len], SIZ - len, "\" <");
410                                 len = strlen(mailto);
411                                 urlesc(&mailto[len], SIZ - len, thisvalue);
412                                 len = strlen(mailto);
413                                 urlesc(&mailto[len], SIZ - len, ">");
414
415                                 strcat(mailto, "\">");
416                                 len = strlen(mailto);
417                                 stresc(mailto+len, SIZ - len, thisvalue, 1, 1);
418                                 strcat(mailto, "</A>");
419                         }
420                         else if (!strcasecmp(firsttoken, "tel")) {
421                                 if (!IsEmptyStr(phone)) strcat(phone, "<br />");
422                                 strcat(phone, thisvalue);
423                                 for (j=0; j<num_tokens(thisname, ';'); ++j) {
424                                         extract_token(buf, thisname, j, ';', sizeof buf);
425                                         if (!strcasecmp(buf, "tel"))
426                                                 strcat(phone, "");
427                                         else if (!strcasecmp(buf, "work"))
428                                                 strcat(phone, _(" (work)"));
429                                         else if (!strcasecmp(buf, "home"))
430                                                 strcat(phone, _(" (home)"));
431                                         else if (!strcasecmp(buf, "cell"))
432                                                 strcat(phone, _(" (cell)"));
433                                         else {
434                                                 strcat(phone, " (");
435                                                 strcat(phone, buf);
436                                                 strcat(phone, ")");
437                                         }
438                                 }
439                         }
440                         else if (!strcasecmp(firsttoken, "adr")) {
441                                 if (pass == 2) {
442                                         StrBufAppendPrintf(Target, "<TR><TD>");
443                                         StrBufAppendPrintf(Target, _("Address:"));
444                                         StrBufAppendPrintf(Target, "</TD><TD>");
445                                         for (j=0; j<num_tokens(thisvalue, ';'); ++j) {
446                                                 extract_token(buf, thisvalue, j, ';', sizeof buf);
447                                                 if (!IsEmptyStr(buf)) {
448                                                         StrEscAppend(Target, NULL, buf, 0, 0);
449                                                         if (j<3) StrBufAppendPrintf(Target, "<br />");
450                                                         else StrBufAppendPrintf(Target, " ");
451                                                 }
452                                         }
453                                         StrBufAppendPrintf(Target, "</TD></TR>\n");
454                                 }
455                         }
456                         /* else if (!strcasecmp(firsttoken, "photo") && full && pass == 2) { 
457                                 // Only output on second pass
458                                 StrBufAppendPrintf(Target, "<tr><td>");
459                                 StrBufAppendPrintf(Target, _("Photo:"));
460                                 StrBufAppendPrintf(Target, "</td><td>");
461                                 StrBufAppendPrintf(Target, "<img src=\"/vcardphoto/%ld/\" alt=\"Contact photo\"/>",msgnum);
462                                 StrBufAppendPrintf(Target, "</td></tr>\n");
463                         } */
464                         else if (!strcasecmp(firsttoken, "version")) {
465                                 /* ignore */
466                         }
467                         else if (!strcasecmp(firsttoken, "rev")) {
468                                 /* ignore */
469                         }
470                         else if (!strcasecmp(firsttoken, "label")) {
471                                 /* ignore */
472                         }
473                         else {
474
475                                 /*** Don't show extra fields.  They're ugly.
476                                 if (pass == 2) {
477                                         StrBufAppendPrintf(Target, "<TR><TD>");
478                                         StrEscAppend(Target, NULL, thisname, 0, 0);
479                                         StrBufAppendPrintf(Target, "</TD><TD>");
480                                         StrEscAppend(Target, NULL, thisvalue, 0, 0);
481                                         StrBufAppendPrintf(Target, "</TD></TR>\n");
482                                 }
483                                 ***/
484                         }
485         
486                         free(thisname);
487                         free(thisvalue);
488                 }
489         
490                 if (pass == 1) {
491                         StrBufAppendPrintf(Target, "<TR BGCOLOR=\"#AAAAAA\">"
492                         "<TD COLSPAN=2 BGCOLOR=\"#FFFFFF\">"
493                         "<IMG ALIGN=CENTER src=\"static/viewcontacts_48x.gif\">"
494                         "<FONT SIZE=+1><B>");
495                         StrEscAppend(Target, NULL, fullname, 0, 0);
496                         StrBufAppendPrintf(Target, "</B></FONT>");
497                         if (!IsEmptyStr(title)) {
498                                 StrBufAppendPrintf(Target, "<div align=right>");
499                                 StrEscAppend(Target, NULL, title, 0, 0);
500                                 StrBufAppendPrintf(Target, "</div>");
501                         }
502                         if (!IsEmptyStr(org)) {
503                                 StrBufAppendPrintf(Target, "<div align=right>");
504                                 StrEscAppend(Target, NULL, org, 0, 0);
505                                 StrBufAppendPrintf(Target, "</div>");
506                         }
507                         StrBufAppendPrintf(Target, "</TD></TR>\n");
508                 
509                         if (!IsEmptyStr(phone)) {
510                                 StrBufAppendPrintf(Target, "<tr><td>");
511                                 StrBufAppendPrintf(Target, _("Telephone:"));
512                                 StrBufAppendPrintf(Target, "</td><td>%s</td></tr>\n", phone);
513                         }
514                         if (!IsEmptyStr(mailto)) {
515                                 StrBufAppendPrintf(Target, "<tr><td>");
516                                 StrBufAppendPrintf(Target, _("E-mail:"));
517                                 StrBufAppendPrintf(Target, "</td><td>%s</td></tr>\n", mailto);
518                         }
519                 }
520
521         }
522
523         StrBufAppendPrintf(Target, "</table></div>\n");
524 }
525
526
527
528 /*
529  * Display a textual vCard
530  * (Converts to a vCard object and then calls the actual display function)
531  * Set 'full' to nonzero to display the whole card instead of a one-liner.
532  * Or, if "storename" is non-NULL, just store the person's name in that
533  * buffer instead of displaying the card at all.
534  *
535  * vcard_source the buffer containing the vcard text
536  * alpha        Display only if name begins with this letter of the alphabet
537  * full         Display the full vCard (otherwise just the display name)
538  * storename    If not NULL, also store the display name here
539  * msgnum       Citadel message pointer
540  */
541 void display_vcard(StrBuf *Target, 
542                    StrBuf *vcard_source, 
543                    char alpha, 
544                    int full, 
545                    char **storename, 
546                    long msgnum) 
547 {
548         struct vCard *v;
549         char *name;
550         StrBuf *Buf;
551         StrBuf *Buf2;
552         char this_alpha = 0;
553
554         v = VCardLoad(vcard_source);
555
556         if (v == NULL) return;
557
558         name = vcard_get_prop(v, "n", 1, 0, 0);
559         if (name != NULL) {
560                 Buf = NewStrBufPlain(name, -1);
561                 Buf2 = NewStrBufPlain(NULL, StrLength(Buf));
562                 StrBuf_RFC822_to_Utf8(Buf2, Buf, WC->DefaultCharset, NULL);
563                 this_alpha = ChrPtr(Buf)[0];
564                 FreeStrBuf(&Buf);
565                 FreeStrBuf(&Buf2);
566         }
567
568         if (storename != NULL) {
569                 fetchname_parsed_vcard(v, storename);
570         }
571         else if (
572                 (alpha == 0)
573                 || ((isalpha(alpha)) && (tolower(alpha) == tolower(this_alpha)))
574                 || ((!isalpha(alpha)) && (!isalpha(this_alpha)))
575         ) {
576                 display_parsed_vcard(Target, v, full,msgnum);
577         }
578
579         vcard_free(v);
580 }
581
582
583
584 /*
585  * Render the address book using info we gathered during the scan
586  *
587  * addrbook     the addressbook to render
588  * num_ab       the number of the addressbook
589  */
590 void do_addrbook_view(addrbookent *addrbook, int num_ab) {
591         int i = 0;
592         int displayed = 0;
593         int bg = 0;
594         static int NAMESPERPAGE = 60;
595         int num_pages = 0;
596         int tabfirst = 0;
597         char tabfirst_label[64];
598         int tablast = 0;
599         char tablast_label[64];
600         char this_tablabel[64];
601         int page = 0;
602         char **tablabels;
603
604         if (num_ab == 0) {
605                 wprintf("<br /><br /><br /><div align=\"center\"><i>");
606                 wprintf(_("This address book is empty."));
607                 wprintf("</i></div>\n");
608                 return;
609         }
610
611         if (num_ab > 1) {
612                 qsort(addrbook, num_ab, sizeof(addrbookent), abcmp);
613         }
614
615         num_pages = (num_ab / NAMESPERPAGE) + 1;
616
617         tablabels = malloc(num_pages * sizeof (char *));
618         if (tablabels == NULL) {
619                 wprintf("<br /><br /><br /><div align=\"center\"><i>");
620                 wprintf(_("An internal error has occurred."));
621                 wprintf("</i></div>\n");
622                 return;
623         }
624
625         for (i=0; i<num_pages; ++i) {
626                 tabfirst = i * NAMESPERPAGE;
627                 tablast = tabfirst + NAMESPERPAGE - 1;
628                 if (tablast > (num_ab - 1)) tablast = (num_ab - 1);
629                 nametab(tabfirst_label, 64, addrbook[tabfirst].ab_name);
630                 nametab(tablast_label, 64, addrbook[tablast].ab_name);
631                 sprintf(this_tablabel, "%s&nbsp;-&nbsp;%s", tabfirst_label, tablast_label);
632                 tablabels[i] = strdup(this_tablabel);
633         }
634
635         tabbed_dialog(num_pages, tablabels);
636         page = (-1);
637
638         for (i=0; i<num_ab; ++i) {
639
640                 if ((i / NAMESPERPAGE) != page) {       /* New tab */
641                         page = (i / NAMESPERPAGE);
642                         if (page > 0) {
643                                 wprintf("</tr></table>\n");
644                                 end_tab(page-1, num_pages);
645                         }
646                         begin_tab(page, num_pages);
647                         wprintf("<table border=0 cellspacing=0 cellpadding=3 width=100%%>\n");
648                         displayed = 0;
649                 }
650
651                 if ((displayed % 4) == 0) {
652                         if (displayed > 0) {
653                                 wprintf("</tr>\n");
654                         }
655                         bg = 1 - bg;
656                         wprintf("<tr bgcolor=\"#%s\">",
657                                 (bg ? "DDDDDD" : "FFFFFF")
658                         );
659                 }
660         
661                 wprintf("<td>");
662
663                 wprintf("<a href=\"readfwd?startmsg=%ld?is_singlecard=1",
664                         addrbook[i].ab_msgnum);
665                 wprintf("?maxmsgs=1?is_summary=0?alpha=%s\">", bstr("alpha"));
666                 vcard_n_prettyize(addrbook[i].ab_name);
667                 escputs(addrbook[i].ab_name);
668                 wprintf("</a></td>\n");
669                 ++displayed;
670         }
671
672         /* Placeholders for empty columns at end */
673         if ((num_ab % 4) != 0) {
674                 for (i=0; i<(4-(num_ab % 4)); ++i) {
675                         wprintf("<td>&nbsp;</td>");
676                 }
677         }
678
679         wprintf("</tr></table>\n");
680         end_tab((num_pages-1), num_pages);
681
682         begin_tab(num_pages, num_pages);
683         /* FIXME there ought to be something here */
684         end_tab(num_pages, num_pages);
685
686         for (i=0; i<num_pages; ++i) {
687                 free(tablabels[i]);
688         }
689         free(tablabels);
690 }
691
692
693
694
695 /*
696  * Edit the vCard component of a MIME message.  
697  * Supply the message number
698  * and MIME part number to fetch.  Or, specify -1 for the message number
699  * to start with a blank card.
700  */
701 void do_edit_vcard(long msgnum, char *partnum, 
702                    message_summary *VCMsg,
703                    wc_mime_attachment *VCAtt,
704                    const char *return_to, 
705                    const char *force_room) {
706         message_summary *Msg = NULL;
707         wc_mime_attachment *VCMime = NULL;
708         struct vCard *v;
709         int i;
710         char *key, *value;
711         char whatuser[256];
712
713         char lastname[256];
714         char firstname[256];
715         char middlename[256];
716         char prefix[256];
717         char suffix[256];
718         char pobox[256];
719         char extadr[256];
720         char street[256];
721         char city[256];
722         char state[256];
723         char zipcode[256];
724         char country[256];
725         char hometel[256];
726         char worktel[256];
727         char faxtel[256];
728         char mobiletel[256];
729         char primary_inetemail[256];
730         char other_inetemail[SIZ];
731         char extrafields[SIZ];
732         char fullname[256];
733         char title[256];
734         char org[256];
735
736         lastname[0] = 0;
737         firstname[0] = 0;
738         middlename[0] = 0;
739         prefix[0] = 0;
740         suffix[0] = 0;
741         pobox[0] = 0;
742         extadr[0] = 0;
743         street[0] = 0;
744         city[0] = 0;
745         state[0] = 0;
746         zipcode[0] = 0;
747         country[0] = 0;
748         hometel[0] = 0;
749         worktel[0] = 0;
750         faxtel[0] = 0;
751         mobiletel[0] = 0;
752         primary_inetemail[0] = 0;
753         other_inetemail[0] = 0;
754         title[0] = 0;
755         org[0] = 0;
756         extrafields[0] = 0;
757         fullname[0] = 0;
758
759         safestrncpy(whatuser, "", sizeof whatuser);
760
761         if ((msgnum >= 0) || 
762             ((VCMsg != NULL) && (VCAtt != NULL)))
763         {
764                 if ((VCMsg == NULL) && (VCAtt == NULL)) {
765
766                         Msg = (message_summary *) malloc(sizeof(message_summary));
767                         memset(Msg, 0, sizeof(message_summary));
768                         Msg->msgnum = msgnum;
769                         VCMime = load_vcard(Msg);
770                         if (VCMime == NULL) {
771                                 convenience_page("770000", _("Error"), "");///TODO: important message
772                                 DestroyMessageSummary(Msg);
773                                 return;
774                         }
775                 
776                         v = VCardLoad(VCMime->Data);
777                 }
778                 else {
779                         v = VCardLoad(VCAtt->Data);
780                 }
781         
782                 /* Populate the variables for our form */
783                 i = 0;
784                 while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
785                         char prp[256];  /* property name */
786                         char prm[256];  /* parameters */
787
788                         value = vcard_get_prop(v, "", 0, i++, 0);
789
790
791                         extract_token(prp, key, 0, ';', sizeof prp);
792                         safestrncpy(prm, key, sizeof prm);
793                         remove_token(prm, 0, ';');
794
795                         if (!strcasecmp(prp, "n")) {
796                                 extract_token(lastname, value, 0, ';', sizeof lastname);
797                                 extract_token(firstname, value, 1, ';', sizeof firstname);
798                                 extract_token(middlename, value, 2, ';', sizeof middlename);
799                                 extract_token(prefix, value, 3, ';', sizeof prefix);
800                                 extract_token(suffix, value, 4, ';', sizeof suffix);
801                         }
802
803                         else if (!strcasecmp(prp, "fn")) {
804                                 safestrncpy(fullname, value, sizeof fullname);
805                         }
806
807                         else if (!strcasecmp(prp, "title")) {
808                                 safestrncpy(title, value, sizeof title);
809                         }
810         
811                         else if (!strcasecmp(prp, "org")) {
812                                 safestrncpy(org, value, sizeof org);
813                         }
814         
815                         else if (!strcasecmp(prp, "adr")) {
816                                 extract_token(pobox, value, 0, ';', sizeof pobox);
817                                 extract_token(extadr, value, 1, ';', sizeof extadr);
818                                 extract_token(street, value, 2, ';', sizeof street);
819                                 extract_token(city, value, 3, ';', sizeof city);
820                                 extract_token(state, value, 4, ';', sizeof state);
821                                 extract_token(zipcode, value, 5, ';', sizeof zipcode);
822                                 extract_token(country, value, 6, ';', sizeof country);
823                         }
824
825                         else if (!strcasecmp(prp, "tel")) {
826
827                                 if (bmstrcasestr(prm, "home")) {
828                                         extract_token(hometel, value, 0, ';', sizeof hometel);
829                                 }
830                                 else if (bmstrcasestr(prm, "work")) {
831                                         extract_token(worktel, value, 0, ';', sizeof worktel);
832                                 }
833                                 else if (bmstrcasestr(prm, "fax")) {
834                                         extract_token(faxtel, value, 0, ';', sizeof faxtel);
835                                 }
836                                 else if (bmstrcasestr(prm, "cell")) {
837                                         extract_token(mobiletel, value, 0, ';', sizeof mobiletel);
838                                 }
839                                 else {  /* Missing or unknown type; put it in the home phone */
840                                         extract_token(hometel, value, 0, ';', sizeof hometel);
841                                 }
842                         }
843         
844                         else if ( (!strcasecmp(prp, "email")) && (bmstrcasestr(prm, "internet")) ) {
845                                 if (primary_inetemail[0] == 0) {
846                                         safestrncpy(primary_inetemail, value, sizeof primary_inetemail);
847                                 }
848                                 else {
849                                         if (other_inetemail[0] != 0) {
850                                                 strcat(other_inetemail, "\n");
851                                         }
852                                         strcat(other_inetemail, value);
853                                 }
854                         }
855
856                         /* Unrecognized properties are preserved here so we don't discard them
857                          * just because the vCard was edited with WebCit.
858                          */
859                         else {
860                                 strcat(extrafields, key);
861                                 strcat(extrafields, ":");
862                                 strcat(extrafields, value);
863                                 strcat(extrafields, "\n");
864                         }
865         
866                 }
867         
868                 vcard_free(v);
869         }
870
871         /* Display the form */
872         output_headers(1, 1, 1, 0, 0, 0);
873
874         svput("BOXTITLE", WCS_STRING, _("Edit contact information"));
875         do_template("beginboxx", NULL);
876
877         wprintf("<form method=\"POST\" action=\"submit_vcard\">\n");
878         wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WC->nonce);
879
880         if (force_room != NULL) {
881                 wprintf("<input type=\"hidden\" name=\"force_room\" value=\"");
882                 escputs(force_room);
883                 wprintf("\">\n");
884         }
885
886         wprintf("<div class=\"fix_scrollbar_bug\">"
887                 "<table class=\"vcard_edit_background\"><tr><td>\n");
888
889         wprintf("<table border=0><tr>"
890                 "<td>%s</td>"
891                 "<td>%s</td>"
892                 "<td>%s</td>"
893                 "<td>%s</td>"
894                 "<td>%s</td></tr>\n",
895                 _("Prefix"), _("First"), _("Middle"), _("Last"), _("Suffix")
896         );
897         wprintf("<tr><td><input type=\"text\" name=\"prefix\" "
898                 "value=\"%s\" maxlength=\"5\" size=\"5\"></td>",
899                 prefix);
900         wprintf("<td><input type=\"text\" name=\"firstname\" "
901                 "value=\"%s\" maxlength=\"29\"></td>",
902                 firstname);
903         wprintf("<td><input type=\"text\" name=\"middlename\" "
904                 "value=\"%s\" maxlength=\"29\"></td>",
905                 middlename);
906         wprintf("<td><input type=\"text\" name=\"lastname\" "
907                 "value=\"%s\" maxlength=\"29\"></td>",
908                 lastname);
909         wprintf("<td><input type=\"text\" name=\"suffix\" "
910                 "value=\"%s\" maxlength=\"10\" size=\"10\"></td></tr></table>\n",
911                 suffix);
912
913         wprintf("<table  class=\"vcard_edit_background_alt\">");
914         wprintf("<tr><td>");
915
916         wprintf(_("Display name:"));
917         wprintf("<br>"
918                 "<input type=\"text\" name=\"fullname\" "
919                 "value=\"%s\" maxlength=\"40\"><br><br>\n",
920                 fullname
921         );
922
923         wprintf(_("Title:"));
924         wprintf("<br>"
925                 "<input type=\"text\" name=\"title\" "
926                 "value=\"%s\" maxlength=\"40\"><br><br>\n",
927                 title
928         );
929
930         wprintf(_("Organization:"));
931         wprintf("<br>"
932                 "<input type=\"text\" name=\"org\" "
933                 "value=\"%s\" maxlength=\"40\"><br><br>\n",
934                 org
935         );
936
937         wprintf("</td><td>");
938
939         wprintf("<table border=0>");
940         wprintf("<tr><td>");
941         wprintf(_("PO box:"));
942         wprintf("</td><td>"
943                 "<input type=\"text\" name=\"pobox\" "
944                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
945                 pobox);
946         wprintf("<tr><td>");
947         wprintf(_("Address:"));
948         wprintf("</td><td>"
949                 "<input type=\"text\" name=\"extadr\" "
950                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
951                 extadr);
952         wprintf("<tr><td> </td><td>"
953                 "<input type=\"text\" name=\"street\" "
954                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
955                 street);
956         wprintf("<tr><td>");
957         wprintf(_("City:"));
958         wprintf("</td><td>"
959                 "<input type=\"text\" name=\"city\" "
960                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
961                 city);
962         wprintf("<tr><td>");
963         wprintf(_("State:"));
964         wprintf("</td><td>"
965                 "<input type=\"text\" name=\"state\" "
966                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
967                 state);
968         wprintf("<tr><td>");
969         wprintf(_("ZIP code:"));
970         wprintf("</td><td>"
971                 "<input type=\"text\" name=\"zipcode\" "
972                 "value=\"%s\" maxlength=\"10\"></td></tr>\n",
973                 zipcode);
974         wprintf("<tr><td>");
975         wprintf(_("Country:"));
976         wprintf("</td><td>"
977                 "<input type=\"text\" name=\"country\" "
978                 "value=\"%s\" maxlength=\"29\" width=\"5\"></td></tr>\n",
979                 country);
980         wprintf("</table>\n");
981
982         wprintf("</table>\n");
983
984         wprintf("<table border=0><tr><td>");
985         wprintf(_("Home telephone:"));
986         wprintf("</td>"
987                 "<td><input type=\"text\" name=\"hometel\" "
988                 "value=\"%s\" maxlength=\"29\"></td>\n",
989                 hometel);
990         wprintf("<td>");
991         wprintf(_("Work telephone:"));
992         wprintf("</td>"
993                 "<td><input type=\"text\" name=\"worktel\" "
994                 "value=\"%s\" maxlength=\"29\"></td></tr>\n",
995                 worktel);
996         wprintf("<tr><td>");
997         wprintf(_("Mobile telephone:"));
998         wprintf("</td>"
999                 "<td><input type=\"text\" name=\"mobiletel\" "
1000                 "value=\"%s\" maxlength=\"29\"></td>\n",
1001                 mobiletel);
1002         wprintf("<td>");
1003         wprintf(_("Fax number:"));
1004         wprintf("</td>"
1005                 "<td><input type=\"text\" name=\"faxtel\" "
1006                 "value=\"%s\" maxlength=\"29\"></td></tr></table>\n",
1007                 faxtel);
1008
1009         wprintf("<table class=\"vcard_edit_background_alt\">");
1010         wprintf("<tr><td>");
1011
1012         wprintf("<table border=0><TR>"
1013                 "<td valign=top>");
1014         wprintf(_("Primary Internet e-mail address"));
1015         wprintf("<br />"
1016                 "<input type=\"text\" name=\"primary_inetemail\" "
1017                 "size=40 maxlength=60 value=\"");
1018         escputs(primary_inetemail);
1019         wprintf("\"><br />"
1020                 "</td><td valign=top>");
1021         wprintf(_("Internet e-mail aliases"));
1022         wprintf("<br />"
1023                 "<textarea name=\"other_inetemail\" rows=5 cols=40 width=40>");
1024         escputs(other_inetemail);
1025         wprintf("</textarea></td></tr></table>\n");
1026
1027         wprintf("</td></tr></table>\n");
1028
1029         wprintf("<input type=\"hidden\" name=\"extrafields\" value=\"");
1030         escputs(extrafields);
1031         wprintf("\">\n");
1032
1033         wprintf("<input type=\"hidden\" name=\"return_to\" value=\"");
1034         escputs(return_to);
1035         wprintf("\">\n");
1036
1037         wprintf("<div class=\"buttons\">\n"
1038                 "<input type=\"submit\" name=\"ok_button\" value=\"%s\">"
1039                 "&nbsp;"
1040                 "<input type=\"submit\" name=\"cancel_button\" value=\"%s\">"
1041                 "</div></form>\n",
1042                 _("Save changes"),
1043                 _("Cancel")
1044         );
1045         
1046         wprintf("</td></tr></table>\n");
1047         do_template("endbox", NULL);
1048         wDumpContent(1);
1049         if (Msg != NULL) {
1050                 DestroyMessageSummary(Msg);
1051         }
1052 }
1053
1054
1055 /*
1056  *  commit the edits to the citadel server
1057  */
1058 void edit_vcard(void) {
1059         long msgnum;
1060         char *partnum;
1061
1062         msgnum = lbstr("msgnum");
1063         partnum = bstr("partnum");
1064         do_edit_vcard(msgnum, partnum, NULL, NULL, "", NULL);
1065 }
1066
1067
1068
1069 /*
1070  *  parse edited vcard from the browser
1071  */
1072 void submit_vcard(void) {
1073         wcsession *WCC = WC;
1074         struct vCard *v;
1075         char *serialized_vcard;
1076         char buf[SIZ];
1077         StrBuf *Buf;
1078         int i;
1079
1080         if (!havebstr("ok_button")) { 
1081                 readloop(readnew);
1082                 return;
1083         }
1084
1085         if (havebstr("force_room")) {
1086                 if (gotoroom(sbstr("force_room")) != 200) {
1087                         StrBufAppendBufPlain(WCC->ImportantMsg,
1088                                              _("Unable to enter the room to save your message"),
1089                                              -1, 0);
1090                         StrBufAppendBufPlain(WCC->ImportantMsg,
1091                                              HKEY(": "), 0);
1092                         StrBufAppendBuf(WCC->ImportantMsg, sbstr("force_room"), 0);
1093                         StrBufAppendBufPlain(WCC->ImportantMsg,
1094                                              HKEY("; "), 0);
1095                                                
1096                         StrBufAppendBufPlain(WCC->ImportantMsg,
1097                                              _("Aborting."),
1098                                              -1, 0);
1099
1100                         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
1101                                 select_user_to_edit(NULL);
1102                         }
1103                         else if (!strcmp(bstr("return_to"), "do_welcome")) {
1104                                 do_welcome();
1105                         }
1106                         else if (!IsEmptyStr(bstr("return_to"))) {
1107                                 http_redirect(bstr("return_to"));
1108                         }
1109                         else {
1110                                 readloop(readnew);
1111                         }
1112                         return;
1113                 }
1114         }
1115
1116         sprintf(buf, "ENT0 1|||4||");
1117         serv_puts(buf);
1118         serv_getln(buf, sizeof buf);
1119         if (buf[0] != '4') {
1120                 edit_vcard();
1121                 return;
1122         }
1123
1124         /* Make a vCard structure out of the data supplied in the form */
1125         Buf = NewStrBuf();
1126         StrBufPrintf(Buf, "begin:vcard\r\n%s\r\nend:vcard\r\n",
1127                      bstr("extrafields")
1128         );
1129         v = VCardLoad(Buf);     /* Start with the extra fields */
1130         FreeStrBuf(&Buf);
1131         if (v == NULL) {
1132                 safestrncpy(WCC->ImportantMessage,
1133                         _("An error has occurred."),
1134                         sizeof WCC->ImportantMessage
1135                 );
1136                 edit_vcard();
1137                 return;
1138         }
1139
1140         snprintf(buf, sizeof buf, "%s;%s;%s;%s;%s",
1141                 bstr("lastname"),
1142                 bstr("firstname"),
1143                 bstr("middlename"),
1144                 bstr("prefix"),
1145                 bstr("suffix") );
1146         vcard_add_prop(v, "n", buf);
1147         
1148         vcard_add_prop(v, "title", bstr("title"));
1149         vcard_add_prop(v, "fn", bstr("fullname"));
1150         vcard_add_prop(v, "org", bstr("org"));
1151
1152         snprintf(buf, sizeof buf, "%s;%s;%s;%s;%s;%s;%s",
1153                 bstr("pobox"),
1154                 bstr("extadr"),
1155                 bstr("street"),
1156                 bstr("city"),
1157                 bstr("state"),
1158                 bstr("zipcode"),
1159                 bstr("country") );
1160         vcard_add_prop(v, "adr", buf);
1161
1162         vcard_add_prop(v, "tel;home", bstr("hometel"));
1163         vcard_add_prop(v, "tel;work", bstr("worktel"));
1164         vcard_add_prop(v, "tel;fax", bstr("faxtel"));
1165         vcard_add_prop(v, "tel;cell", bstr("mobiletel"));
1166         vcard_add_prop(v, "email;internet", bstr("primary_inetemail"));
1167
1168         for (i=0; i<num_tokens(bstr("other_inetemail"), '\n'); ++i) {
1169                 extract_token(buf, bstr("other_inetemail"), i, '\n', sizeof buf);
1170                 if (!IsEmptyStr(buf)) {
1171                         vcard_add_prop(v, "email;internet", buf);
1172                 }
1173         }
1174
1175         serialized_vcard = vcard_serialize(v);
1176         vcard_free(v);
1177         if (serialized_vcard == NULL) {
1178                 safestrncpy(WCC->ImportantMessage,
1179                         _("An error has occurred."),
1180                         sizeof WCC->ImportantMessage
1181                 );
1182                 edit_vcard();
1183                 return;
1184         }
1185
1186         serv_puts("Content-type: text/x-vcard; charset=UTF-8");
1187         serv_puts("");
1188         serv_printf("%s\r\n", serialized_vcard);
1189         serv_puts("000");
1190         free(serialized_vcard);
1191
1192         if (!strcmp(bstr("return_to"), "select_user_to_edit")) {
1193                 select_user_to_edit(NULL);
1194         }
1195         else if (!strcmp(bstr("return_to"), "do_welcome")) {
1196                 do_welcome();
1197         }
1198         else if (!IsEmptyStr(bstr("return_to"))) {
1199                 http_redirect(bstr("return_to"));
1200         }
1201         else {
1202                 readloop(readnew);
1203         }
1204 }
1205
1206
1207
1208 /*
1209  * Extract an embedded photo from a vCard for display on the client
1210  */
1211 void display_vcard_photo_img(void)
1212 {
1213         long msgnum = 0L;
1214         StrBuf *vcard;
1215         struct vCard *v;
1216         char *photosrc;
1217         const char *contentType;
1218         wcsession *WCC = WC;
1219
1220         msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
1221         
1222         vcard = load_mimepart(msgnum,"1");
1223         v = VCardLoad(vcard);
1224         
1225         photosrc = vcard_get_prop(v, "PHOTO", 1,0,0);
1226         FlushStrBuf(WCC->WBuf);
1227         StrBufAppendBufPlain(WCC->WBuf, photosrc, -1, 0);
1228         if (StrBufDecodeBase64(WCC->WBuf) <= 0) {
1229                 FlushStrBuf(WCC->WBuf);
1230                 
1231                 hprintf("HTTP/1.1 500 %s\n","Unable to get photo");
1232                 output_headers(0, 0, 0, 0, 0, 0);
1233                 hprintf("Content-Type: text/plain\r\n");
1234                 begin_burst();
1235                 wprintf(_("Could Not decode vcard photo\n"));
1236                 end_burst();
1237                 return;
1238         }
1239         contentType = GuessMimeType(ChrPtr(WCC->WBuf), StrLength(WCC->WBuf));
1240         http_transmit_thing(contentType, 0);
1241         free(v);
1242         free(photosrc);
1243 }
1244
1245 typedef struct _vcardview_struct {
1246         long is_singlecard;
1247         addrbookent *addrbook;
1248         long num_ab;
1249
1250 } vcardview_struct;
1251
1252 int vcard_GetParamsGetServerCall(SharedMessageStatus *Stat, 
1253                                  void **ViewSpecific, 
1254                                  long oper, 
1255                                  char *cmd, 
1256                                  long len)
1257 {
1258         vcardview_struct *VS;
1259
1260         VS = (vcardview_struct*) malloc (sizeof(vcardview_struct));
1261         memset(VS, 0, sizeof(vcardview_struct));
1262         *ViewSpecific = (void*)VS;
1263
1264         VS->is_singlecard = ibstr("is_singlecard");
1265         if (VS->is_singlecard != 1) {
1266                 if (oper == do_search) {
1267                         snprintf(cmd, len, "MSGS SEARCH|%s", bstr("query"));
1268                 }
1269                 else {
1270                         strcpy(cmd, "MSGS ALL");
1271                 }
1272                 Stat->maxmsgs = 9999999;
1273         }
1274         return 200;
1275 }
1276
1277 int vcard_LoadMsgFromServer(SharedMessageStatus *Stat, 
1278                             void **ViewSpecific, 
1279                             message_summary* Msg, 
1280                             int is_new, 
1281                             int i)
1282 {
1283         vcardview_struct *VS;
1284         char *ab_name;
1285
1286         VS = (vcardview_struct*) *ViewSpecific;
1287
1288         ab_name = NULL;
1289         fetch_ab_name(Msg, &ab_name);
1290         if (ab_name == NULL) 
1291                 return 0;
1292         ++VS->num_ab;
1293         VS->addrbook = realloc(VS->addrbook,
1294                                (sizeof(addrbookent) * VS->num_ab) );
1295         safestrncpy(VS->addrbook[VS->num_ab-1].ab_name, ab_name,
1296                     sizeof(VS->addrbook[VS->num_ab-1].ab_name));
1297         VS->addrbook[VS->num_ab-1].ab_msgnum = Msg->msgnum;
1298         free(ab_name);
1299         return 0;
1300 }
1301
1302
1303 int vcard_RenderView_or_Tail(SharedMessageStatus *Stat, void **ViewSpecific, long oper)
1304 {
1305         const StrBuf *Mime;
1306         vcardview_struct *VS;
1307
1308         VS = (vcardview_struct*) *ViewSpecific;
1309         if (VS->is_singlecard)
1310                 read_message(WC->WBuf, HKEY("view_message"), lbstr("startmsg"), NULL, &Mime);
1311         else
1312                 do_addrbook_view(VS->addrbook, VS->num_ab);     /* Render the address book */
1313         return 0;
1314 }
1315
1316 int vcard_Cleanup(void **ViewSpecific)
1317 {
1318         vcardview_struct *VS;
1319
1320         VS = (vcardview_struct*) *ViewSpecific;
1321         wDumpContent(1);
1322         if ((VS != NULL) && 
1323             (VS->addrbook != NULL))
1324                 free(VS->addrbook);
1325         if (VS != NULL) 
1326                 free(VS);
1327         return 0;
1328 }
1329
1330 void 
1331 InitModule_VCARD
1332 (void)
1333 {
1334         RegisterReadLoopHandlerset(
1335                 VIEW_ADDRESSBOOK,
1336                 vcard_GetParamsGetServerCall,
1337                 NULL,
1338                 vcard_LoadMsgFromServer,
1339                 vcard_RenderView_or_Tail,
1340                 vcard_Cleanup);
1341         WebcitAddUrlHandler(HKEY("edit_vcard"), "", 0, edit_vcard, 0);
1342         WebcitAddUrlHandler(HKEY("submit_vcard"), "", 0, submit_vcard, 0);
1343         WebcitAddUrlHandler(HKEY("vcardphoto"), "", 0, display_vcard_photo_img, NEED_URL);
1344 }
1345