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