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