* All OS-level includes are now included from webcit.h instead of from
[citadel.git] / webcit / messages.c
1 /*
2  * $Id$
3  *
4  * Functions which deal with the fetching and displaying of messages.
5  *
6  */
7
8 #include "webcit.h"
9 #include "vcard.h"
10 #include "webserver.h"
11
12
13 /* Address book entry (keep it short and sweet, it's just a quickie lookup
14  * which we can use to get to the real meat and bones later)
15  */
16 struct addrbookent {
17         char ab_name[64];
18         long ab_msgnum;
19 };
20
21
22
23 #ifdef HAVE_ICONV
24 /* Handle subjects with RFC2047 encoding, such as:
25  * =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
26  */
27 void utf8ify_rfc822_string(char *buf) {
28         char *start, *end;
29         char newbuf[1024];
30         char charset[128];
31         char encoding[16];
32         char istr[1024];
33         iconv_t ic = (iconv_t)(-1) ;
34         char *ibuf;                   /* Buffer of characters to be converted */
35         char *obuf;                   /* Buffer for converted characters      */
36         size_t ibuflen;               /* Length of input buffer               */
37         size_t obuflen;               /* Length of output buffer              */
38         char *isav;                   /* Saved pointer to input buffer        */
39         char *osav;                   /* Saved pointer to output buffer       */
40
41         while (start=strstr(buf, "=?"), end=strstr(buf, "?="),
42                 ((start != NULL) && (end != NULL) && (end > start)) )
43         {
44                 extract_token(charset, start, 1, '?', sizeof charset);
45                 extract_token(encoding, start, 2, '?', sizeof encoding);
46                 extract_token(istr, start, 3, '?', sizeof istr);
47
48                 /*strcpy(start, "");
49                 ++end;
50                 ++end;*/
51
52                 ibuf = malloc(1024);
53                 isav = ibuf;
54                 if (!strcasecmp(encoding, "B")) {       /* base64 */
55                         ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
56                 }
57                 else if (!strcasecmp(encoding, "Q")) {  /* quoted-printable */
58                         ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, strlen(istr));
59                 }
60                 else {
61                         strcpy(ibuf, istr);             /* huh? */
62                         ibuflen = strlen(istr);
63                 }
64
65                 ic = iconv_open("UTF-8", charset);
66                 if (ic != (iconv_t)(-1) ) {
67                         obuf = malloc(1024);
68                         obuflen = 1024;
69                         obuf = (char *) malloc(obuflen);
70                         osav = obuf;
71                         iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
72                         osav[1024-obuflen] = 0;
73
74                         end = start;
75                         end++;
76                         strcpy(start, "");
77                         remove_token(end, 0, '?');
78                         remove_token(end, 0, '?');
79                         remove_token(end, 0, '?');
80                         remove_token(end, 0, '?');
81                         strcpy(end, &end[1]);
82
83                         snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
84                         strcpy(buf, newbuf);
85                         free(osav);
86                         iconv_close(ic);
87                 }
88                 else {
89                         snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
90                         strcpy(buf, newbuf);
91                 }
92
93                 free(isav);
94         }
95
96 }
97 #endif
98
99
100 /*
101  * Look for URL's embedded in a buffer and make them linkable.  We use a
102  * target window in order to keep the BBS session in its own window.
103  */
104 void url(buf)
105 char buf[];
106 {
107
108         int pos;
109         int start, end;
110         char ench;
111         char urlbuf[SIZ];
112         char outbuf[1024];
113
114         start = (-1);
115         end = strlen(buf);
116         ench = 0;
117
118         for (pos = 0; pos < strlen(buf); ++pos) {
119                 if (!strncasecmp(&buf[pos], "http://", 7))
120                         start = pos;
121                 if (!strncasecmp(&buf[pos], "ftp://", 6))
122                         start = pos;
123         }
124
125         if (start < 0)
126                 return;
127
128         if ((start > 0) && (buf[start - 1] == '<'))
129                 ench = '>';
130         if ((start > 0) && (buf[start - 1] == '['))
131                 ench = ']';
132         if ((start > 0) && (buf[start - 1] == '('))
133                 ench = ')';
134         if ((start > 0) && (buf[start - 1] == '{'))
135                 ench = '}';
136
137         for (pos = strlen(buf); pos > start; --pos) {
138                 if ((buf[pos] == ' ') || (buf[pos] == ench))
139                         end = pos;
140         }
141
142         strncpy(urlbuf, &buf[start], end - start);
143         urlbuf[end - start] = 0;
144
145         strncpy(outbuf, buf, start);
146         sprintf(&outbuf[start], "%cA HREF=%c%s%c TARGET=%c%s%c%c%s%c/A%c",
147                 LB, QU, urlbuf, QU, QU, TARGET, QU, RB, urlbuf, LB, RB);
148         strcat(outbuf, &buf[end]);
149         if ( strlen(outbuf) < 250 )
150                 strcpy(buf, outbuf);
151 }
152
153
154 /*
155  * Turn a vCard "n" (name) field into something displayable.
156  */
157 void vcard_n_prettyize(char *name)
158 {
159         char *original_name;
160         int i;
161
162         original_name = strdup(name);
163         for (i=0; i<5; ++i) {
164                 if (strlen(original_name) > 0) {
165                         if (original_name[strlen(original_name)-1] == ' ') {
166                                 original_name[strlen(original_name)-1] = 0;
167                         }
168                         if (original_name[strlen(original_name)-1] == ';') {
169                                 original_name[strlen(original_name)-1] = 0;
170                         }
171                 }
172         }
173         strcpy(name, "");
174         for (i=0; i<strlen(original_name); ++i) {
175                 if (original_name[i] == ';') {
176                         strcat(name, ", ");
177                 }
178                 else {
179                         name[strlen(name)+1] = 0;
180                         name[strlen(name)] = original_name[i];
181                 }
182         }
183         free(original_name);
184 }
185
186
187
188
189 /* display_vcard() calls this after parsing the textual vCard into
190  * our 'struct vCard' data object.
191  * This gets called instead of display_parsed_vcard() if we are only looking
192  * to extract the person's name instead of displaying the card.
193  */
194 void fetchname_parsed_vcard(struct vCard *v, char *storename) {
195         char *name;
196
197         strcpy(storename, "");
198
199         name = vcard_get_prop(v, "n", 1, 0, 0);
200         if (name != NULL) {
201                 strcpy(storename, name);
202                 /* vcard_n_prettyize(storename); */
203         }
204
205 }
206
207
208
209 /* display_vcard() calls this after parsing the textual vCard into
210  * our 'struct vCard' data object.
211  *
212  * Set 'full' to nonzero to display the full card, otherwise it will only
213  * show a summary line.
214  *
215  * This code is a bit ugly, so perhaps an explanation is due: we do this
216  * in two passes through the vCard fields.  On the first pass, we process
217  * fields we understand, and then render them in a pretty fashion at the
218  * end.  Then we make a second pass, outputting all the fields we don't
219  * understand in a simple two-column name/value format.
220  */
221 void display_parsed_vcard(struct vCard *v, int full) {
222         int i, j;
223         char buf[SIZ];
224         char *name;
225         int is_qp = 0;
226         int is_b64 = 0;
227         char *thisname, *thisvalue;
228         char firsttoken[SIZ];
229         int pass;
230
231         char displayname[SIZ];
232         char title[SIZ];
233         char org[SIZ];
234         char phone[SIZ];
235         char mailto[SIZ];
236
237         strcpy(displayname, "");
238         strcpy(phone, "");
239         strcpy(mailto, "");
240         strcpy(title, "");
241         strcpy(org, "");
242
243         if (!full) {
244                 wprintf("<TD>");
245                 name = vcard_get_prop(v, "fn", 1, 0, 0);
246                 if (name != NULL) {
247                         escputs(name);
248                 }
249                 else if (name = vcard_get_prop(v, "n", 1, 0, 0), name != NULL) {
250                         strcpy(displayname, name);
251                         vcard_n_prettyize(displayname);
252                         escputs(displayname);
253                 }
254                 else {
255                         wprintf("&nbsp;");
256                 }
257                 wprintf("</TD>");
258                 return;
259         }
260
261         wprintf("<div align=center><table bgcolor=#aaaaaa width=50%%>");
262         for (pass=1; pass<=2; ++pass) {
263
264                 if (v->numprops) for (i=0; i<(v->numprops); ++i) {
265
266                         thisname = strdup(v->prop[i].name);
267                         extract_token(firsttoken, thisname, 0, ';', sizeof firsttoken);
268         
269                         for (j=0; j<num_tokens(thisname, ';'); ++j) {
270                                 extract_token(buf, thisname, j, ';', sizeof buf);
271                                 if (!strcasecmp(buf, "encoding=quoted-printable")) {
272                                         is_qp = 1;
273                                         remove_token(thisname, j, ';');
274                                 }
275                                 if (!strcasecmp(buf, "encoding=base64")) {
276                                         is_b64 = 1;
277                                         remove_token(thisname, j, ';');
278                                 }
279                         }
280         
281                         if (is_qp) {
282                                 thisvalue = malloc(strlen(v->prop[i].value) + 50);
283                                 j = CtdlDecodeQuotedPrintable(
284                                         thisvalue, v->prop[i].value,
285                                         strlen(v->prop[i].value) );
286                                 thisvalue[j] = 0;
287                         }
288                         else if (is_b64) {
289                                 thisvalue = malloc(strlen(v->prop[i].value) + 50);
290                                 CtdlDecodeBase64(
291                                         thisvalue, v->prop[i].value,
292                                         strlen(v->prop[i].value) );
293                         }
294                         else {
295                                 thisvalue = strdup(v->prop[i].value);
296                         }
297         
298                         /*** Various fields we may encounter ***/
299         
300                         /* N is name, but only if there's no FN already there */
301                         if (!strcasecmp(firsttoken, "n")) {
302                                 if (strlen(displayname) == 0) {
303                                         strcpy(displayname, thisvalue);
304                                         vcard_n_prettyize(displayname);
305                                 }
306                         }
307         
308                         /* FN (full name) is a true 'display name' field */
309                         else if (!strcasecmp(firsttoken, "fn")) {
310                                 strcpy(displayname, thisvalue);
311                         }
312
313                         /* title */
314                         else if (!strcasecmp(firsttoken, "title")) {
315                                 strcpy(title, thisvalue);
316                         }
317         
318                         /* organization */
319                         else if (!strcasecmp(firsttoken, "org")) {
320                                 strcpy(org, thisvalue);
321                         }
322         
323                         else if (!strcasecmp(firsttoken, "email")) {
324                                 if (strlen(mailto) > 0) strcat(mailto, "<br />");
325                                 strcat(mailto,
326                                         "<A HREF=\"/display_enter"
327                                         "?force_room=_MAIL_?recp=");
328                                 urlesc(&mailto[strlen(mailto)], thisvalue);
329                                 strcat(mailto, "\">");
330                                 urlesc(&mailto[strlen(mailto)], thisvalue);
331                                 strcat(mailto, "</A>");
332                         }
333                         else if (!strcasecmp(firsttoken, "tel")) {
334                                 if (strlen(phone) > 0) strcat(phone, "<br />");
335                                 strcat(phone, thisvalue);
336                                 for (j=0; j<num_tokens(thisname, ';'); ++j) {
337                                         extract_token(buf, thisname, j, ';', sizeof buf);
338                                         if (!strcasecmp(buf, "tel"))
339                                                 strcat(phone, "");
340                                         else if (!strcasecmp(buf, "work"))
341                                                 strcat(phone, " (work)");
342                                         else if (!strcasecmp(buf, "home"))
343                                                 strcat(phone, " (home)");
344                                         else if (!strcasecmp(buf, "cell"))
345                                                 strcat(phone, " (cell)");
346                                         else {
347                                                 strcat(phone, " (");
348                                                 strcat(phone, buf);
349                                                 strcat(phone, ")");
350                                         }
351                                 }
352                         }
353                         else if (!strcasecmp(firsttoken, "adr")) {
354                                 if (pass == 2) {
355                                         wprintf("<TR><TD>Address:</TD><TD>");
356                                         for (j=0; j<num_tokens(thisvalue, ';'); ++j) {
357                                                 extract_token(buf, thisvalue, j, ';', sizeof buf);
358                                                 if (strlen(buf) > 0) {
359                                                         escputs(buf);
360                                                         if (j<3) wprintf("<br />");
361                                                         else wprintf(" ");
362                                                 }
363                                         }
364                                         wprintf("</TD></TR>\n");
365                                 }
366                         }
367                         else if (!strcasecmp(firsttoken, "version")) {
368                                 /* ignore */
369                         }
370                         else if (!strcasecmp(firsttoken, "rev")) {
371                                 /* ignore */
372                         }
373                         else if (!strcasecmp(firsttoken, "label")) {
374                                 /* ignore */
375                         }
376                         else {
377
378                                 /*** Don't show extra fields.  They're ugly.
379                                 if (pass == 2) {
380                                         wprintf("<TR><TD>");
381                                         escputs(thisname);
382                                         wprintf("</TD><TD>");
383                                         escputs(thisvalue);
384                                         wprintf("</TD></TR>\n");
385                                 }
386                                 ***/
387                         }
388         
389                         free(thisname);
390                         free(thisvalue);
391                 }
392         
393                 if (pass == 1) {
394                         wprintf("<TR BGCOLOR=\"#AAAAAA\">"
395                         "<TD COLSPAN=2 BGCOLOR=\"#FFFFFF\">"
396                         "<IMG ALIGN=CENTER SRC=\"/static/viewcontacts_48x.gif\">"
397                         "<FONT SIZE=+1><B>");
398                         escputs(displayname);
399                         wprintf("</B></FONT>");
400                         if (strlen(title) > 0) {
401                                 wprintf("<div align=right>");
402                                 escputs(title);
403                                 wprintf("</div>");
404                         }
405                         if (strlen(org) > 0) {
406                                 wprintf("<div align=right>");
407                                 escputs(org);
408                                 wprintf("</div>");
409                         }
410                         wprintf("</TD></TR>\n");
411                 
412                         if (strlen(phone) > 0)
413                                 wprintf("<TR><TD>Telephone:</TD><TD>%s</TD></TR>\n", phone);
414                         if (strlen(mailto) > 0)
415                                 wprintf("<TR><TD>E-mail:</TD><TD>%s</TD></TR>\n", mailto);
416                 }
417
418         }
419
420         wprintf("</table></div>\n");
421 }
422
423
424
425 /*
426  * Display a textual vCard
427  * (Converts to a vCard object and then calls the actual display function)
428  * Set 'full' to nonzero to display the whole card instead of a one-liner.
429  * Or, if "storename" is non-NULL, just store the person's name in that
430  * buffer instead of displaying the card at all.
431  */
432 void display_vcard(char *vcard_source, char alpha, int full, char *storename) {
433         struct vCard *v;
434         char *name;
435         char buf[SIZ];
436         char this_alpha = 0;
437
438         v = vcard_load(vcard_source);
439         if (v == NULL) return;
440
441         name = vcard_get_prop(v, "n", 1, 0, 0);
442         if (name != NULL) {
443                 strcpy(buf, name);
444                 this_alpha = buf[0];
445         }
446
447         if (storename != NULL) {
448                 fetchname_parsed_vcard(v, storename);
449         }
450         else if (       (alpha == 0)
451                         || ((isalpha(alpha)) && (tolower(alpha) == tolower(this_alpha)) )
452                         || ((!isalpha(alpha)) && (!isalpha(this_alpha)))
453                 ) {
454                 display_parsed_vcard(v, full);
455         }
456
457         vcard_free(v);
458 }
459
460
461
462
463 /*
464  * I wanna SEE that message!
465  */
466 void read_message(long msgnum, int suppress_buttons) {
467         char buf[SIZ];
468         char mime_partnum[256];
469         char mime_filename[256];
470         char mime_content_type[256];
471         char mime_charset[256];
472         char mime_disposition[256];
473         int mime_length;
474         char mime_http[SIZ];
475         char m_subject[256];
476         char from[256];
477         char node[256];
478         char rfca[256];
479         char reply_to[512];
480         char now[256];
481         int format_type = 0;
482         int nhdr = 0;
483         int bq = 0;
484         int i = 0;
485         char vcard_partnum[256];
486         char cal_partnum[256];
487         char *part_source = NULL;
488 #ifdef HAVE_ICONV
489         iconv_t ic = (iconv_t)(-1) ;
490         char *ibuf;                   /* Buffer of characters to be converted */
491         char *obuf;                   /* Buffer for converted characters      */
492         size_t ibuflen;               /* Length of input buffer               */
493         size_t obuflen;               /* Length of output buffer              */
494         char *osav;                   /* Saved pointer to output buffer       */
495 #endif
496
497         strcpy(from, "");
498         strcpy(node, "");
499         strcpy(rfca, "");
500         strcpy(reply_to, "");
501         strcpy(vcard_partnum, "");
502         strcpy(cal_partnum, "");
503         strcpy(mime_http, "");
504         strcpy(mime_content_type, "text/plain");
505         strcpy(mime_charset, "us-ascii");
506
507         serv_printf("MSG4 %ld", msgnum);
508         serv_getln(buf, sizeof buf);
509         if (buf[0] != '1') {
510                 wprintf("<STRONG>ERROR:</STRONG> %s<br />\n", &buf[4]);
511                 return;
512         }
513
514         /* begin everythingamundo table */
515         wprintf("<div id=\"fix_scrollbar_bug\">\n");
516         wprintf("<table width=100%% border=1 cellspacing=0 "
517                 "cellpadding=0><TR><TD>\n");
518
519         /* begin message header table */
520         wprintf("<TABLE WIDTH=100%% BORDER=0 CELLSPACING=0 "
521                 "CELLPADDING=1 BGCOLOR=\"#CCCCCC\"><TR><TD>\n");
522
523         wprintf("<SPAN CLASS=\"message_header\">");
524         strcpy(m_subject, "");
525
526         while (serv_getln(buf, sizeof buf), strcasecmp(buf, "text")) {
527                 if (!strcmp(buf, "000")) {
528                         wprintf("<I>unexpected end of message</I><br /><br />\n");
529                         wprintf("</SPAN>\n");
530                         return;
531                 }
532                 if (!strncasecmp(buf, "nhdr=yes", 8))
533                         nhdr = 1;
534                 if (nhdr == 1)
535                         buf[0] = '_';
536                 if (!strncasecmp(buf, "type=", 5))
537                         format_type = atoi(&buf[5]);
538                 if (!strncasecmp(buf, "from=", 5)) {
539                         strcpy(from, &buf[5]);
540                         wprintf("from <A HREF=\"/showuser?who=");
541 #ifdef HAVE_ICONV
542                         utf8ify_rfc822_string(from);
543 #endif
544                         urlescputs(from);
545                         wprintf("\">");
546                         escputs(from);
547                         wprintf("</A> ");
548                 }
549                 if (!strncasecmp(buf, "subj=", 5))
550                         strcpy(m_subject, &buf[5]);
551                 if ((!strncasecmp(buf, "hnod=", 5))
552                     && (strcasecmp(&buf[5], serv_info.serv_humannode)))
553                         wprintf("(%s) ", &buf[5]);
554                 if ((!strncasecmp(buf, "room=", 5))
555                     && (strcasecmp(&buf[5], WC->wc_roomname))
556                     && (strlen(&buf[5])>0) )
557                         wprintf("in %s> ", &buf[5]);
558                 if (!strncasecmp(buf, "rfca=", 5)) {
559                         strcpy(rfca, &buf[5]);
560                         wprintf("&lt;");
561                         escputs(rfca);
562                         wprintf("&gt; ");
563                 }
564
565                 if (!strncasecmp(buf, "node=", 5)) {
566                         strcpy(node, &buf[5]);
567                         if ( ((WC->room_flags & QR_NETWORK)
568                         || ((strcasecmp(&buf[5], serv_info.serv_nodename)
569                         && (strcasecmp(&buf[5], serv_info.serv_fqdn)))))
570                         && (strlen(rfca)==0)
571                         ) {
572                                 wprintf("@%s ", &buf[5]);
573                         }
574                 }
575                 if (!strncasecmp(buf, "rcpt=", 5))
576                         wprintf("to %s ", &buf[5]);
577                 if (!strncasecmp(buf, "time=", 5)) {
578                         fmt_date(now, atol(&buf[5]), 0);
579                         wprintf("%s ", now);
580                 }
581
582                 if (!strncasecmp(buf, "part=", 5)) {
583                         extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
584                         extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
585                         extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
586                         extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
587                         mime_length = extract_int(&buf[5], 5);
588
589                         if (!strcasecmp(mime_disposition, "attachment")) {
590                                 snprintf(&mime_http[strlen(mime_http)],
591                                         (sizeof(mime_http) - strlen(mime_http) - 1),
592                                         "<A HREF=\"/output_mimepart?"
593                                         "msgnum=%ld?partnum=%s\" "
594                                         "TARGET=\"wc.%ld.%s\">"
595                                         "<IMG SRC=\"/static/diskette_24x.gif\" "
596                                         "BORDER=0 ALIGN=MIDDLE>\n"
597                                         "Part %s: %s (%s, %d bytes)</A><br />\n",
598                                         msgnum, mime_partnum,
599                                         msgnum, mime_partnum,
600                                         mime_partnum, mime_filename,
601                                         mime_content_type, mime_length);
602                         }
603
604                         if ((!strcasecmp(mime_disposition, "inline"))
605                            && (!strncasecmp(mime_content_type, "image/", 6)) ){
606                                 snprintf(&mime_http[strlen(mime_http)],
607                                         (sizeof(mime_http) - strlen(mime_http) - 1),
608                                         "<IMG SRC=\"/output_mimepart?"
609                                         "msgnum=%ld?partnum=%s\">",
610                                         msgnum, mime_partnum);
611                         }
612
613                         /*** begin handler prep ***/
614                         if (!strcasecmp(mime_content_type, "text/x-vcard")) {
615                                 strcpy(vcard_partnum, mime_partnum);
616                         }
617
618                         if (!strcasecmp(mime_content_type, "text/calendar")) {
619                                 strcpy(cal_partnum, mime_partnum);
620                         }
621
622                         /*** end handler prep ***/
623
624                 }
625
626         }
627
628         /* Generate a reply-to address */
629         if (strlen(rfca) > 0) {
630                 strcpy(reply_to, rfca);
631         }
632         else {
633                 if ( (strlen(node) > 0)
634                    && (strcasecmp(node, serv_info.serv_nodename))
635                    && (strcasecmp(node, serv_info.serv_humannode)) ) {
636                         snprintf(reply_to, sizeof(reply_to), "%s @ %s",
637                                 from, node);
638                 }
639                 else {
640                         snprintf(reply_to, sizeof(reply_to), "%s", from);
641                 }
642         }
643
644         if (nhdr == 1) {
645                 wprintf("****");
646         }
647
648         wprintf("</SPAN>");
649 #ifdef HAVE_ICONV
650         utf8ify_rfc822_string(m_subject);
651 #endif
652         if (strlen(m_subject) > 0) {
653                 wprintf("<br />"
654                         "<SPAN CLASS=\"message_subject\">"
655                         "Subject: %s"
656                         "</SPAN>", m_subject
657                 );
658         }
659         wprintf("</TD>\n");
660
661         /* start msg buttons */
662         if (!suppress_buttons) {
663                 wprintf("<td align=right>\n");
664
665                 /* Reply */
666                 wprintf("<a href=\"/display_enter?recp=");
667                 urlescputs(reply_to);
668                 wprintf("?subject=");
669                 if (strncasecmp(m_subject, "Re:", 3)) wprintf("Re:%20");
670                 urlescputs(m_subject);
671                 wprintf("\">[Reply]</a> ");
672
673                 if (WC->is_room_aide)  {
674                 
675                         /* Move */
676                         wprintf("<a href=\"/confirm_move_msg?msgid=%ld\">[Move]</a> ",
677                                 msgnum);
678         
679                         /* Delete */
680                         wprintf("<a href=\"/delete_msg?msgid=%ld\" "
681                                 "onClick=\"return confirm('Delete this message?');\">"
682                                 "[Delete]</a> ", msgnum);
683                                 
684                 }
685
686                 wprintf("<a href=\"/msg?msgnum=%ld?print_it=yes\" target=\"msgloader1\">"
687                         "[Print]</a>", msgnum);
688
689                 wprintf("</td>");
690         }
691
692         wprintf("</TR></TABLE>\n");
693
694         /* Begin body */
695         wprintf("<TABLE BORDER=0 WIDTH=100%% BGCOLOR=#FFFFFF "
696                 "CELLPADDING=1 CELLSPACING=0><TR><TD>");
697
698         /* 
699          * Learn the content type
700          */
701         strcpy(mime_content_type, "text/plain");
702         while (serv_getln(buf, sizeof buf), (strlen(buf) > 0)) {
703                 if (!strcmp(buf, "000")) {
704                         wprintf("<I>unexpected end of message</I><br /><br />\n");
705                         goto ENDBODY;
706                 }
707                 if (!strncasecmp(buf, "Content-type: ", 14)) {
708                         safestrncpy(mime_content_type, &buf[14],
709                                 sizeof(mime_content_type));
710                         for (i=0; i<strlen(mime_content_type); ++i) {
711                                 if (!strncasecmp(&mime_content_type[i], "charset=", 8)) {
712                                         safestrncpy(mime_charset, &mime_content_type[i+8],
713                                                 sizeof mime_charset);
714                                 }
715                         }
716                         for (i=0; i<strlen(mime_content_type); ++i) {
717                                 if (mime_content_type[i] == ';') {
718                                         mime_content_type[i] = 0;
719                                 }
720                         }
721                 }
722         }
723
724         /* Set up a character set conversion if we need to (and if we can) */
725 #ifdef HAVE_ICONV
726         if ( (strcasecmp(mime_charset, "us-ascii"))
727            && (strcasecmp(mime_charset, "UTF-8")) ) {
728                 ic = iconv_open("UTF-8", mime_charset);
729                 if (ic == (iconv_t)(-1) ) {
730                         lprintf(5, "iconv_open() failed: %s\n", strerror(errno));
731                 }
732         }
733 #endif
734
735         /* Messages in legacy Citadel variformat get handled thusly... */
736         if (!strcasecmp(mime_content_type, "text/x-citadel-variformat")) {
737                 fmout(NULL, "JUSTIFY");
738         }
739
740         /* Boring old 80-column fixed format text gets handled this way... */
741         else if (!strcasecmp(mime_content_type, "text/plain")) {
742                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
743                         if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = 0;
744                         if (buf[strlen(buf)-1] == '\r') buf[strlen(buf)-1] = 0;
745
746 #ifdef HAVE_ICONV
747                         if (ic != (iconv_t)(-1) ) {
748                                 ibuf = buf;
749                                 ibuflen = strlen(ibuf);
750                                 obuflen = SIZ;
751                                 obuf = (char *) malloc(obuflen);
752                                 osav = obuf;
753                                 iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
754                                 osav[SIZ-obuflen] = 0;
755                                 safestrncpy(buf, osav, sizeof buf);
756                                 free(osav);
757                         }
758 #endif
759
760                         while ((strlen(buf) > 0) && (isspace(buf[strlen(buf) - 1])))
761                                 buf[strlen(buf) - 1] = 0;
762                         if ((bq == 0) &&
763                         ((!strncmp(buf, ">", 1)) || (!strncmp(buf, " >", 2)) || (!strncmp(buf, " :-)", 4)))) {
764                                 wprintf("<BLOCKQUOTE>");
765                                 bq = 1;
766                         } else if ((bq == 1) &&
767                                 (strncmp(buf, ">", 1)) && (strncmp(buf, " >", 2)) && (strncmp(buf, " :-)", 4))) {
768                                 wprintf("</BLOCKQUOTE>");
769                                 bq = 0;
770                         }
771                         wprintf("<TT>");
772                         url(buf);
773                         escputs(buf);
774                         wprintf("</TT><br />\n");
775                 }
776                 wprintf("</I><br />");
777         }
778
779         else /* HTML is fun, but we've got to strip it first */
780         if (!strcasecmp(mime_content_type, "text/html")) {
781                 output_html(mime_charset);
782         }
783
784         /* Unknown weirdness */
785         else {
786                 wprintf("I don't know how to display %s<br />\n",
787                         mime_content_type);
788                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) { }
789         }
790
791         /* Afterwards, offer links to download attachments 'n' such */
792         if (strlen(mime_http) > 0) {
793                 wprintf("%s", mime_http);
794         }
795
796         /* Handler for vCard parts */
797         if (strlen(vcard_partnum) > 0) {
798                 part_source = load_mimepart(msgnum, vcard_partnum);
799                 if (part_source != NULL) {
800
801                         /* If it's my vCard I can edit it */
802                         if (    (!strcasecmp(WC->wc_roomname, USERCONFIGROOM))
803                                 || (!strcasecmp(&WC->wc_roomname[11], USERCONFIGROOM))
804                                 || (WC->wc_view == VIEW_ADDRESSBOOK)
805                         ) {
806                                 wprintf("<A HREF=\"/edit_vcard?"
807                                         "msgnum=%ld?partnum=%s\">",
808                                         msgnum, vcard_partnum);
809                                 wprintf("[edit]</A>");
810                         }
811
812                         /* In all cases, display the full card */
813                         display_vcard(part_source, 0, 1, NULL);
814                 }
815         }
816
817         /* Handler for calendar parts */
818         if (strlen(cal_partnum) > 0) {
819                 part_source = load_mimepart(msgnum, cal_partnum);
820                 if (part_source != NULL) {
821                         cal_process_attachment(part_source,
822                                                 msgnum, cal_partnum);
823                 }
824         }
825
826         if (part_source) {
827                 free(part_source);
828                 part_source = NULL;
829         }
830
831 ENDBODY:
832         wprintf("</TD></TR></TABLE>\n");
833
834         /* end everythingamundo table */
835         wprintf("</TD></TR></TABLE>\n");
836         wprintf("</div><br />\n");
837
838 #ifdef HAVE_ICONV
839         if (ic != (iconv_t)(-1) ) {
840                 iconv_close(ic);
841         }
842 #endif
843 }
844
845
846
847 /*
848  * Unadorned HTML output of an individual message, suitable
849  * for placing in a hidden iframe, for printing, or whatever
850  */
851 void embed_message(void) {
852         long msgnum = 0L;
853         char *sourceiframe;
854         char *targetdiv;
855         char *print_it;
856
857         msgnum = atol(bstr("msgnum"));
858         sourceiframe = bstr("sourceiframe");
859         targetdiv = bstr("targetdiv");
860         print_it = bstr("print_it");
861
862         output_headers(1, 0, 0, 0, 0, 1, 0);
863         begin_burst();
864
865         wprintf("<html><head>");
866
867         /* If we're loading into a hidden iframe, chances are the caller told us
868          * about a target div somewhere that we need to copy into when we're done.
869          */
870         if (strlen(targetdiv) > 0) wprintf(
871 "                                                                       \n"
872 " <script type=\"text/javascript\">                                     \n"
873 "       function loaded_now_copy_it() {                                 \n"
874 "               parent.document.getElementById(\"%s\").innerHTML = parent.frames['%s'].document.body.innerHTML; \n"
875 "       }                                                                                       \n"
876 "</script>\n",
877                 targetdiv,
878                 sourceiframe
879         );
880
881         wprintf("</head>");
882         wprintf("<body");
883         if (strlen(targetdiv) > 0) {
884                 wprintf(" onLoad='loaded_now_copy_it();'");
885         }
886         if (!strcasecmp(print_it, "yes")) {
887                 wprintf(" onLoad='window.print();'");
888         }
889         wprintf(">\n");
890         read_message(msgnum, (!strcasecmp(print_it, "yes") ? 1 : 0) );
891         wprintf("</body></html>\n");
892         wDumpContent(0);
893 }
894
895
896
897
898 void display_summarized(int num) {
899         char datebuf[64];
900
901         wprintf("<TD>");
902         if (WC->summ[num].is_new) wprintf("<B>");
903         wprintf("<A HREF=\"/msg?msgnum=%ld?sourceiframe=msgloader1?targetdiv=preview_pane\" target=\"msgloader1\">",
904                 WC->summ[num].msgnum);
905         escputs(WC->summ[num].subj);
906         wprintf("</A>");
907         if (WC->summ[num].is_new) wprintf("</B>");
908         wprintf("</TD><TD>");
909         if (WC->summ[num].is_new) wprintf("<B>");
910         escputs(WC->summ[num].from);
911         if (WC->summ[num].is_new) wprintf("</B>");
912         wprintf(" </TD><TD>");
913         if (WC->summ[num].is_new) wprintf("<B>");
914         fmt_date(datebuf, WC->summ[num].date, 1);       /* brief */
915         escputs(datebuf);
916         if (WC->summ[num].is_new) wprintf("</B>");
917         wprintf(" </TD>");
918         wprintf("<TD>"
919                 "<INPUT TYPE=\"checkbox\" NAME=\"msg_%ld\" VALUE=\"yes\">"
920                 "</TD>\n",
921                 WC->summ[num].msgnum
922         );
923 }
924
925
926
927
928
929 void display_addressbook(long msgnum, char alpha) {
930         char buf[SIZ];
931         char mime_partnum[SIZ];
932         char mime_filename[SIZ];
933         char mime_content_type[SIZ];
934         char mime_disposition[SIZ];
935         int mime_length;
936         char vcard_partnum[SIZ];
937         char *vcard_source = NULL;
938         struct message_summary summ;
939
940         memset(&summ, 0, sizeof(summ));
941         safestrncpy(summ.subj, "(no subject)", sizeof summ.subj);
942
943         sprintf(buf, "MSG0 %ld|1", msgnum);     /* ask for headers only */
944         serv_puts(buf);
945         serv_getln(buf, sizeof buf);
946         if (buf[0] != '1') return;
947
948         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
949                 if (!strncasecmp(buf, "part=", 5)) {
950                         extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
951                         extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
952                         extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
953                         extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
954                         mime_length = extract_int(&buf[5], 5);
955
956                         if (!strcasecmp(mime_content_type, "text/x-vcard")) {
957                                 strcpy(vcard_partnum, mime_partnum);
958                         }
959
960                 }
961         }
962
963         if (strlen(vcard_partnum) > 0) {
964                 vcard_source = load_mimepart(msgnum, vcard_partnum);
965                 if (vcard_source != NULL) {
966
967                         /* Display the summary line */
968                         display_vcard(vcard_source, alpha, 0, NULL);
969
970                         /* If it's my vCard I can edit it */
971                         if (    (!strcasecmp(WC->wc_roomname, USERCONFIGROOM))
972                                 || (!strcasecmp(&WC->wc_roomname[11], USERCONFIGROOM))
973                                 || (WC->wc_view == VIEW_ADDRESSBOOK)
974                         ) {
975                                 wprintf("<A HREF=\"/edit_vcard?"
976                                         "msgnum=%ld?partnum=%s\">",
977                                         msgnum, vcard_partnum);
978                                 wprintf("[edit]</A>");
979                         }
980
981                         free(vcard_source);
982                 }
983         }
984
985 }
986
987
988
989 /* If it's an old "Firstname Lastname" style record, try to
990  * convert it.
991  */
992 void lastfirst_firstlast(char *namebuf) {
993         char firstname[SIZ];
994         char lastname[SIZ];
995         int i;
996
997         if (namebuf == NULL) return;
998         if (strchr(namebuf, ';') != NULL) return;
999
1000         i = num_tokens(namebuf, ' ');
1001         if (i < 2) return;
1002
1003         extract_token(lastname, namebuf, i-1, ' ', sizeof lastname);
1004         remove_token(namebuf, i-1, ' ');
1005         strcpy(firstname, namebuf);
1006         sprintf(namebuf, "%s; %s", lastname, firstname);
1007 }
1008
1009
1010 void fetch_ab_name(long msgnum, char *namebuf) {
1011         char buf[SIZ];
1012         char mime_partnum[SIZ];
1013         char mime_filename[SIZ];
1014         char mime_content_type[SIZ];
1015         char mime_disposition[SIZ];
1016         int mime_length;
1017         char vcard_partnum[SIZ];
1018         char *vcard_source = NULL;
1019         int i;
1020         struct message_summary summ;
1021
1022         if (namebuf == NULL) return;
1023         strcpy(namebuf, "");
1024
1025         memset(&summ, 0, sizeof(summ));
1026         safestrncpy(summ.subj, "(no subject)", sizeof summ.subj);
1027
1028         sprintf(buf, "MSG0 %ld|1", msgnum);     /* ask for headers only */
1029         serv_puts(buf);
1030         serv_getln(buf, sizeof buf);
1031         if (buf[0] != '1') return;
1032
1033         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
1034                 if (!strncasecmp(buf, "part=", 5)) {
1035                         extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
1036                         extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
1037                         extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
1038                         extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
1039                         mime_length = extract_int(&buf[5], 5);
1040
1041                         if (!strcasecmp(mime_content_type, "text/x-vcard")) {
1042                                 strcpy(vcard_partnum, mime_partnum);
1043                         }
1044
1045                 }
1046         }
1047
1048         if (strlen(vcard_partnum) > 0) {
1049                 vcard_source = load_mimepart(msgnum, vcard_partnum);
1050                 if (vcard_source != NULL) {
1051
1052                         /* Grab the name off the card */
1053                         display_vcard(vcard_source, 0, 0, namebuf);
1054
1055                         free(vcard_source);
1056                 }
1057         }
1058
1059         lastfirst_firstlast(namebuf);
1060         striplt(namebuf);
1061         for (i=0; i<strlen(namebuf); ++i) {
1062                 if (namebuf[i] != ';') return;
1063         }
1064         strcpy(namebuf, "(no name)");
1065 }
1066
1067
1068
1069 /*
1070  * Record compare function for sorting address book indices
1071  */
1072 int abcmp(const void *ab1, const void *ab2) {
1073         return(strcasecmp(
1074                 (((const struct addrbookent *)ab1)->ab_name),
1075                 (((const struct addrbookent *)ab2)->ab_name)
1076         ));
1077 }
1078
1079
1080 /*
1081  * Helper function for do_addrbook_view()
1082  * Converts a name into a three-letter tab label
1083  */
1084 void nametab(char *tabbuf, char *name) {
1085         stresc(tabbuf, name, 0, 0);
1086         tabbuf[0] = toupper(tabbuf[0]);
1087         tabbuf[1] = tolower(tabbuf[1]);
1088         tabbuf[2] = tolower(tabbuf[2]);
1089         tabbuf[3] = 0;
1090 }
1091
1092
1093 /*
1094  * Render the address book using info we gathered during the scan
1095  */
1096 void do_addrbook_view(struct addrbookent *addrbook, int num_ab) {
1097         int i = 0;
1098         int displayed = 0;
1099         int bg = 0;
1100         static int NAMESPERPAGE = 60;
1101         int num_pages = 0;
1102         int page = 0;
1103         int tabfirst = 0;
1104         char tabfirst_label[SIZ];
1105         int tablast = 0;
1106         char tablast_label[SIZ];
1107
1108         if (num_ab == 0) {
1109                 wprintf("<I>This address book is empty.</I>\n");
1110                 return;
1111         }
1112
1113         if (num_ab > 1) {
1114                 qsort(addrbook, num_ab, sizeof(struct addrbookent), abcmp);
1115         }
1116
1117         num_pages = num_ab / NAMESPERPAGE;
1118
1119         page = atoi(bstr("page"));
1120
1121         wprintf("Page: ");
1122         for (i=0; i<=num_pages; ++i) {
1123                 if (i != page) {
1124                         wprintf("<A HREF=\"/readfwd?page=%d\">", i);
1125                 }
1126                 else {
1127                         wprintf("<B>");
1128                 }
1129                 tabfirst = i * NAMESPERPAGE;
1130                 tablast = tabfirst + NAMESPERPAGE - 1;
1131                 if (tablast > (num_ab - 1)) tablast = (num_ab - 1);
1132                 nametab(tabfirst_label, addrbook[tabfirst].ab_name);
1133                 nametab(tablast_label, addrbook[tablast].ab_name);
1134                 wprintf("[%s&nbsp;-&nbsp;%s]",
1135                         tabfirst_label, tablast_label
1136                 );
1137                 if (i != page) {
1138                         wprintf("</A>\n");
1139                 }
1140                 else {
1141                         wprintf("</B>\n");
1142                 }
1143         }
1144         wprintf("<br />\n");
1145
1146         wprintf("<TABLE border=0 cellspacing=0 "
1147                 "cellpadding=3 width=100%%>\n"
1148         );
1149
1150         for (i=0; i<num_ab; ++i) {
1151
1152                 if ((i / NAMESPERPAGE) == page) {
1153
1154                         if ((displayed % 4) == 0) {
1155                                 if (displayed > 0) {
1156                                         wprintf("</TR>\n");
1157                                 }
1158                                 bg = 1 - bg;
1159                                 wprintf("<TR BGCOLOR=\"#%s\">",
1160                                         (bg ? "DDDDDD" : "FFFFFF")
1161                                 );
1162                         }
1163         
1164                         wprintf("<TD>");
1165         
1166                         wprintf("<A HREF=\"/readfwd?startmsg=%ld&is_singlecard=1",
1167                                 addrbook[i].ab_msgnum);
1168                         wprintf("?maxmsgs=1?summary=0?alpha=%s\">", bstr("alpha"));
1169                         vcard_n_prettyize(addrbook[i].ab_name);
1170                         escputs(addrbook[i].ab_name);
1171                         wprintf("</A></TD>\n");
1172                         ++displayed;
1173                 }
1174         }
1175
1176         wprintf("</TR></TABLE>\n");
1177 }
1178
1179
1180
1181 /* 
1182  * load message pointers from the server
1183  */
1184 int load_msg_ptrs(char *servcmd, int with_headers)
1185 {
1186         char buf[1024];
1187         time_t datestamp;
1188         char displayname[128];
1189         char nodename[128];
1190         char inetaddr[128];
1191         char subject[256];
1192         int nummsgs;
1193         int maxload = 0;
1194
1195         int num_summ_alloc = 0;
1196
1197         if (with_headers) {
1198                 if (WC->num_summ != 0) {
1199                         free(WC->summ);
1200                         WC->num_summ = 0;
1201                 }
1202         }
1203         num_summ_alloc = 100;
1204         WC->num_summ = 0;
1205         WC->summ = malloc(num_summ_alloc * sizeof(struct message_summary));
1206
1207         nummsgs = 0;
1208         maxload = sizeof(WC->msgarr) / sizeof(long) ;
1209         serv_puts(servcmd);
1210         serv_getln(buf, sizeof buf);
1211         if (buf[0] != '1') {
1212                 wprintf("<EM>%s</EM><br />\n", &buf[4]);
1213                 return (nummsgs);
1214         }
1215         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
1216                 if (nummsgs < maxload) {
1217                         WC->msgarr[nummsgs] = extract_long(buf, 0);
1218                         datestamp = extract_long(buf, 1);
1219                         extract_token(displayname, buf, 2, '|', sizeof displayname);
1220                         extract_token(nodename, buf, 3, '|', sizeof nodename);
1221                         extract_token(inetaddr, buf, 4, '|', sizeof inetaddr);
1222                         extract_token(subject, buf, 5, '|', sizeof subject);
1223                         ++nummsgs;
1224
1225                         if (with_headers) {
1226                                 if (nummsgs > num_summ_alloc) {
1227                                         num_summ_alloc *= 2;
1228                                         WC->summ = realloc(WC->summ, num_summ_alloc * sizeof(struct message_summary));
1229                                 }
1230                                 ++WC->num_summ;
1231
1232                                 memset(&WC->summ[nummsgs-1], 0, sizeof(struct message_summary));
1233                                 WC->summ[nummsgs-1].msgnum = WC->msgarr[nummsgs-1];
1234                                 safestrncpy(WC->summ[nummsgs-1].subj, "(no subject)", sizeof WC->summ[nummsgs-1].subj);
1235                                 if (strlen(displayname) > 0) {
1236                                         safestrncpy(WC->summ[nummsgs-1].from, displayname, sizeof WC->summ[nummsgs-1].from);
1237                                 }
1238                                 if (strlen(subject) > 0) {
1239                                 safestrncpy(WC->summ[nummsgs-1].subj, subject,
1240                                         sizeof WC->summ[nummsgs-1].subj);
1241                                 }
1242 #ifdef HAVE_ICONV
1243                                 /* Handle subjects with RFC2047 encoding */
1244                                 utf8ify_rfc822_string(WC->summ[nummsgs-1].subj);
1245 #endif
1246                                 if (strlen(WC->summ[nummsgs-1].subj) > 75) {
1247                                         strcpy(&WC->summ[nummsgs-1].subj[72], "...");
1248                                 }
1249
1250                                 if (strlen(nodename) > 0) {
1251                                         if ( ((WC->room_flags & QR_NETWORK)
1252                                            || ((strcasecmp(nodename, serv_info.serv_nodename)
1253                                            && (strcasecmp(nodename, serv_info.serv_fqdn)))))
1254                                         ) {
1255                                                 strcat(WC->summ[nummsgs-1].from, " @ ");
1256                                                 strcat(WC->summ[nummsgs-1].from, nodename);
1257                                         }
1258                                 }
1259
1260                                 WC->summ[nummsgs-1].date = datestamp;
1261         
1262 #ifdef HAVE_ICONV
1263                                 /* Handle senders with RFC2047 encoding */
1264                                 utf8ify_rfc822_string(WC->summ[nummsgs-1].from);
1265 #endif
1266                                 if (strlen(WC->summ[nummsgs-1].from) > 25) {
1267                                         strcpy(&WC->summ[nummsgs-1].from[22], "...");
1268                                 }
1269                         }
1270                 }
1271         }
1272         return (nummsgs);
1273 }
1274
1275  
1276 int summcmp_subj(const void *s1, const void *s2) {
1277         struct message_summary *summ1;
1278         struct message_summary *summ2;
1279         
1280         summ1 = (struct message_summary *)s1;
1281         summ2 = (struct message_summary *)s2;
1282         return strcasecmp(summ1->subj, summ2->subj);
1283 }
1284
1285 int summcmp_rsubj(const void *s1, const void *s2) {
1286         struct message_summary *summ1;
1287         struct message_summary *summ2;
1288         
1289         summ1 = (struct message_summary *)s1;
1290         summ2 = (struct message_summary *)s2;
1291         return strcasecmp(summ2->subj, summ1->subj);
1292 }
1293
1294 int summcmp_sender(const void *s1, const void *s2) {
1295         struct message_summary *summ1;
1296         struct message_summary *summ2;
1297         
1298         summ1 = (struct message_summary *)s1;
1299         summ2 = (struct message_summary *)s2;
1300         return strcasecmp(summ1->from, summ2->from);
1301 }
1302
1303 int summcmp_rsender(const void *s1, const void *s2) {
1304         struct message_summary *summ1;
1305         struct message_summary *summ2;
1306         
1307         summ1 = (struct message_summary *)s1;
1308         summ2 = (struct message_summary *)s2;
1309         return strcasecmp(summ2->from, summ1->from);
1310 }
1311
1312 int summcmp_date(const void *s1, const void *s2) {
1313         struct message_summary *summ1;
1314         struct message_summary *summ2;
1315         
1316         summ1 = (struct message_summary *)s1;
1317         summ2 = (struct message_summary *)s2;
1318
1319         if (summ1->date < summ2->date) return -1;
1320         else if (summ1->date > summ2->date) return +1;
1321         else return 0;
1322 }
1323
1324 int summcmp_rdate(const void *s1, const void *s2) {
1325         struct message_summary *summ1;
1326         struct message_summary *summ2;
1327         
1328         summ1 = (struct message_summary *)s1;
1329         summ2 = (struct message_summary *)s2;
1330
1331         if (summ1->date < summ2->date) return +1;
1332         else if (summ1->date > summ2->date) return -1;
1333         else return 0;
1334 }
1335
1336 /*
1337  * command loop for reading messages
1338  */
1339 void readloop(char *oper)
1340 {
1341         char cmd[SIZ];
1342         char buf[SIZ];
1343         char old_msgs[SIZ];
1344         int a, b;
1345         int nummsgs;
1346         long startmsg;
1347         int maxmsgs;
1348         int num_displayed = 0;
1349         int is_summary = 0;
1350         int is_addressbook = 0;
1351         int is_singlecard = 0;
1352         int is_calendar = 0;
1353         int is_tasks = 0;
1354         int is_notes = 0;
1355         int remaining_messages;
1356         int lo, hi;
1357         int lowest_displayed = (-1);
1358         int highest_displayed = 0;
1359         long pn_previous = 0L;
1360         long pn_current = 0L;
1361         long pn_next = 0L;
1362         int bg = 0;
1363         struct addrbookent *addrbook = NULL;
1364         int num_ab = 0;
1365         char *sortby = NULL;
1366         char sortpref_name[128];
1367         char sortpref_value[128];
1368         char *subjsort_button;
1369         char *sendsort_button;
1370         char *datesort_button;
1371
1372         startmsg = atol(bstr("startmsg"));
1373         maxmsgs = atoi(bstr("maxmsgs"));
1374         is_summary = atoi(bstr("summary"));
1375         if (maxmsgs == 0) maxmsgs = DEFAULT_MAXMSGS;
1376
1377         snprintf(sortpref_name, sizeof sortpref_name, "sort %s", WC->wc_roomname);
1378         get_preference(sortpref_name, sortpref_value, sizeof sortpref_value);
1379
1380         sortby = bstr("sortby");
1381         if ( (strlen(sortby) > 0) && (strcasecmp(sortby, sortpref_value)) ) {
1382                 set_preference(sortpref_name, sortby, 1);
1383         }
1384         if (strlen(sortby) == 0) sortby = sortpref_value;
1385         if (strlen(sortby) == 0) sortby = "msgid";
1386
1387         output_headers(1, 1, 1, 0, 0, 0, 0);
1388
1389         /* When in summary mode, always show ALL messages instead of just
1390          * new or old.  Otherwise, show what the user asked for.
1391          */
1392         if (!strcmp(oper, "readnew")) {
1393                 strcpy(cmd, "MSGS NEW");
1394         }
1395         else if (!strcmp(oper, "readold")) {
1396                 strcpy(cmd, "MSGS OLD");
1397         }
1398         else {
1399                 strcpy(cmd, "MSGS ALL");
1400         }
1401
1402         if ((WC->wc_view == VIEW_MAILBOX) && (maxmsgs > 1)) {
1403                 is_summary = 1;
1404                 strcpy(cmd, "MSGS ALL");
1405         }
1406
1407         if ((WC->wc_view == VIEW_ADDRESSBOOK) && (maxmsgs > 1)) {
1408                 is_addressbook = 1;
1409                 strcpy(cmd, "MSGS ALL");
1410                 maxmsgs = 9999999;
1411         }
1412
1413         if (is_summary) {
1414                 strcpy(cmd, "MSGS ALL|||1");    /* fetch header summary */
1415                 startmsg = 1;
1416                 maxmsgs = 9999999;
1417         }
1418
1419         /* Are we doing a summary view?  If so, we need to know old messages
1420          * and new messages, so we can do that pretty boldface thing for the
1421          * new messages.
1422          */
1423         strcpy(old_msgs, "");
1424         if (is_summary) {
1425                 serv_puts("GTSN");
1426                 serv_getln(buf, sizeof buf);
1427                 if (buf[0] == '2') {
1428                         strcpy(old_msgs, &buf[4]);
1429                 }
1430         }
1431
1432         is_singlecard = atoi(bstr("is_singlecard"));
1433
1434         if (WC->wc_view == VIEW_CALENDAR) {             /* calendar */
1435                 is_calendar = 1;
1436                 strcpy(cmd, "MSGS ALL");
1437                 maxmsgs = 32767;
1438         }
1439         if (WC->wc_view == VIEW_TASKS) {                /* tasks */
1440                 is_tasks = 1;
1441                 strcpy(cmd, "MSGS ALL");
1442                 maxmsgs = 32767;
1443         }
1444         if (WC->wc_view == VIEW_NOTES) {                /* notes */
1445                 is_notes = 1;
1446                 strcpy(cmd, "MSGS ALL");
1447                 maxmsgs = 32767;
1448         }
1449
1450         nummsgs = load_msg_ptrs(cmd, is_summary);
1451         if (nummsgs == 0) {
1452
1453                 if ((!is_tasks) && (!is_calendar) && (!is_notes)) {
1454                         if (!strcmp(oper, "readnew")) {
1455                                 wprintf("<EM>No new messages.</EM>\n");
1456                         } else if (!strcmp(oper, "readold")) {
1457                                 wprintf("<EM>No old messages.</EM>\n");
1458                         } else {
1459                                 wprintf("<EM>No messages here.</EM>\n");
1460                         }
1461                 }
1462
1463                 goto DONE;
1464         }
1465
1466         if (is_summary) {
1467                 for (a = 0; a < nummsgs; ++a) {
1468                         /* Are you a new message, or an old message? */
1469                         if (is_summary) {
1470                                 if (is_msg_in_mset(old_msgs, WC->msgarr[a])) {
1471                                         WC->summ[a].is_new = 0;
1472                                 }
1473                                 else {
1474                                         WC->summ[a].is_new = 1;
1475                                 }
1476                         }
1477                 }
1478         }
1479
1480         if (startmsg == 0L) startmsg = WC->msgarr[0];
1481         remaining_messages = 0;
1482
1483         for (a = 0; a < nummsgs; ++a) {
1484                 if (WC->msgarr[a] >= startmsg) {
1485                         ++remaining_messages;
1486                 }
1487         }
1488
1489         if (is_summary) {
1490                 if (!strcasecmp(sortby, "subject")) {
1491                         qsort(WC->summ, WC->num_summ,
1492                                 sizeof(struct message_summary), summcmp_subj);
1493                 }
1494                 else if (!strcasecmp(sortby, "rsubject")) {
1495                         qsort(WC->summ, WC->num_summ,
1496                                 sizeof(struct message_summary), summcmp_rsubj);
1497                 }
1498                 else if (!strcasecmp(sortby, "sender")) {
1499                         qsort(WC->summ, WC->num_summ,
1500                                 sizeof(struct message_summary), summcmp_sender);
1501                 }
1502                 else if (!strcasecmp(sortby, "rsender")) {
1503                         qsort(WC->summ, WC->num_summ,
1504                                 sizeof(struct message_summary), summcmp_rsender);
1505                 }
1506                 else if (!strcasecmp(sortby, "date")) {
1507                         qsort(WC->summ, WC->num_summ,
1508                                 sizeof(struct message_summary), summcmp_date);
1509                 }
1510                 else if (!strcasecmp(sortby, "rdate")) {
1511                         qsort(WC->summ, WC->num_summ,
1512                                 sizeof(struct message_summary), summcmp_rdate);
1513                 }
1514         }
1515
1516         if (!strcasecmp(sortby, "subject")) {
1517                 subjsort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=rsubject\"><img border=\"0\" src=\"/static/down_pointer.gif\" /></a>" ;
1518         }
1519         else if (!strcasecmp(sortby, "rsubject")) {
1520                 subjsort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=subject\"><img border=\"0\" src=\"/static/up_pointer.gif\" /></a>" ;
1521         }
1522         else {
1523                 subjsort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=subject\"><img border=\"0\" src=\"/static/sort_none.gif\" /></a>" ;
1524         }
1525
1526         if (!strcasecmp(sortby, "sender")) {
1527                 sendsort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=rsender\"><img border=\"0\" src=\"/static/down_pointer.gif\" /></a>" ;
1528         }
1529         else if (!strcasecmp(sortby, "rsender")) {
1530                 sendsort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=sender\"><img border=\"0\" src=\"/static/up_pointer.gif\" /></a>" ;
1531         }
1532         else {
1533                 sendsort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=sender\"><img border=\"0\" src=\"/static/sort_none.gif\" /></a>" ;
1534         }
1535
1536         if (!strcasecmp(sortby, "date")) {
1537                 datesort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=rdate\"><img border=\"0\" src=\"/static/down_pointer.gif\" /></a>" ;
1538         }
1539         else if (!strcasecmp(sortby, "rdate")) {
1540                 datesort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=date\"><img border=\"0\" src=\"/static/up_pointer.gif\" /></a>" ;
1541         }
1542         else {
1543                 datesort_button = "<a href=\"/readfwd?startmsg=1?maxmsgs=9999999?summary=1?sortby=date\"><img border=\"0\" src=\"/static/sort_none.gif\" /></a>" ;
1544         }
1545
1546         if (is_summary) {
1547                 wprintf("</div>");              /* end of 'content' div */
1548
1549                 wprintf("<div id=\"message_list\">"
1550
1551                         "<div id=\"fix_scrollbar_bug\">\n"
1552
1553                         "<form name=\"msgomatic\" "
1554                         "method=\"POST\" action=\"/do_stuff_to_msgs\">\n"
1555
1556                         "<table border=0 cellspacing=0 "
1557                         "cellpadding=0 width=100%%>\n"
1558                         "<TR>"
1559                         "<TD align=center><b><i>Subject</i></b> %s</TD>"
1560                         "<TD align=center><b><i>Sender</i></b> %s</TD>"
1561                         "<TD align=center><b><i>Date</i></b> %s</TD>"
1562                         "<TD><INPUT TYPE=\"submit\" NAME=\"sc\" "
1563                         "STYLE=\"font-family: Bitstream Vera Sans,Arial,Helvetica,sans-serif;"
1564                         " font-size: 6pt;\" "
1565                         "VALUE=\"Delete\"></TD>"
1566                         "</TR>\n"
1567                         ,
1568                         subjsort_button,
1569                         sendsort_button,
1570                         datesort_button
1571                 );
1572         }
1573
1574         for (a = 0; a < nummsgs; ++a) {
1575                 if ((WC->msgarr[a] >= startmsg) && (num_displayed < maxmsgs)) {
1576
1577                         /* Learn which msgs "Prev" & "Next" buttons go to */
1578                         pn_current = WC->msgarr[a];
1579                         if (a > 0) pn_previous = WC->msgarr[a-1];
1580                         if (a < (nummsgs-1)) pn_next = WC->msgarr[a+1];
1581
1582                         /* If a tabular view, set up the line */
1583                         if (is_summary) {
1584                                 bg = 1 - bg;
1585                                 wprintf("<TR BGCOLOR=\"#%s\">",
1586                                         (bg ? "DDDDDD" : "FFFFFF")
1587                                 );
1588                         }
1589
1590                         /* Display the message */
1591                         if (is_summary) {
1592                                 display_summarized(a);
1593                         }
1594                         else if (is_addressbook) {
1595                                 fetch_ab_name(WC->msgarr[a], buf);
1596                                 ++num_ab;
1597                                 addrbook = realloc(addrbook,
1598                                         (sizeof(struct addrbookent) * num_ab) );
1599                                 safestrncpy(addrbook[num_ab-1].ab_name, buf,
1600                                         sizeof(addrbook[num_ab-1].ab_name));
1601                                 addrbook[num_ab-1].ab_msgnum = WC->msgarr[a];
1602                         }
1603                         else if (is_calendar) {
1604                                 display_calendar(WC->msgarr[a]);
1605                         }
1606                         else if (is_tasks) {
1607                                 display_task(WC->msgarr[a]);
1608                         }
1609                         else if (is_notes) {
1610                                 display_note(WC->msgarr[a]);
1611                         }
1612                         else {
1613                                 read_message(WC->msgarr[a], 0);
1614                         }
1615
1616                         /* If a tabular view, finish the line */
1617                         if (is_summary) {
1618                                 wprintf("</TR>\n");
1619                         }
1620
1621                         if (lowest_displayed < 0) lowest_displayed = a;
1622                         highest_displayed = a;
1623
1624                         ++num_displayed;
1625                         --remaining_messages;
1626                 }
1627         }
1628
1629         if (is_summary) {
1630                 wprintf("</table></form>"
1631                         "</div>\n");                    /* end of 'fix_scrollbar_bug' div */
1632                 wprintf("</div>");                      /* end of 'message_list' div */
1633
1634                 wprintf("<div id=\"preview_pane\">");   /* The preview pane will initially be empty */
1635         }
1636
1637         /* Bump these because although we're thinking in zero base, the user
1638          * is a drooling idiot and is thinking in one base.
1639          */
1640         ++lowest_displayed;
1641         ++highest_displayed;
1642
1643         /* If we're only looking at one message, do a prev/next thing */
1644         if (num_displayed == 1) {
1645            if ((!is_tasks) && (!is_calendar) && (!is_addressbook) && (!is_notes) && (!is_singlecard)) {
1646
1647                 wprintf("<div id=\"fix_scrollbar_bug\">"
1648                         "<table border=0 width=100%% bgcolor=\"#dddddd\"><tr><td>"
1649                         "Reading #%d of %d messages.</TD>\n"
1650                         "<TD ALIGN=RIGHT><FONT SIZE=+1>",
1651                         lowest_displayed, nummsgs);
1652
1653                 if (pn_previous > 0L) {
1654                         wprintf("<A HREF=\"/%s"
1655                                 "?startmsg=%ld"
1656                                 "?maxmsgs=1"
1657                                 "?summary=0\">"
1658                                 "Previous</A> \n",
1659                                         oper,
1660                                         pn_previous );
1661                 }
1662
1663                 if (pn_next > 0L) {
1664                         wprintf("<A HREF=\"/%s"
1665                                 "?startmsg=%ld"
1666                                 "?maxmsgs=1"
1667                                 "?summary=0\">"
1668                                 "Next</A> \n",
1669                                         oper,
1670                                         pn_next );
1671                 }
1672
1673                 wprintf("<A HREF=\"/%s?startmsg=%ld"
1674                         "?maxmsgs=%d?summary=1\">"
1675                         "Summary"
1676                         "</A>",
1677                         oper,
1678                         WC->msgarr[0],
1679                         DEFAULT_MAXMSGS
1680                 );
1681
1682                 wprintf("</td></tr></table></div>\n");
1683             }
1684         }
1685
1686         /*
1687          * If we're not currently looking at ALL requested
1688          * messages, then display the selector bar
1689          */
1690         if (num_displayed > 1) {
1691            if ((!is_tasks) && (!is_calendar) && (!is_addressbook)
1692               && (!is_notes) && (!is_singlecard) && (!is_summary)) {
1693
1694                 wprintf("<form name=\"msgomatic\" "
1695                         "method=\"POST\" action=\"/do_stuff_to_msgs\">\n");
1696
1697                 wprintf("Reading #", lowest_displayed, highest_displayed);
1698
1699                 wprintf("<select name=\"whichones\" size=\"1\" "
1700                         "OnChange=\"location.href=msgomatic.whichones.options"
1701                         "[selectedIndex].value\">\n");
1702
1703                 for (b=0; b<nummsgs; b = b + maxmsgs) {
1704                 lo = b+1;
1705                 hi = b+maxmsgs;
1706                 if (hi > nummsgs) hi = nummsgs;
1707                         wprintf("<option %s value="
1708                                 "\"/%s"
1709                                 "?startmsg=%ld"
1710                                 "?maxmsgs=%d"
1711                                 "?summary=%d\">"
1712                                 "%d-%d</option> \n",
1713                                 ((WC->msgarr[b] == startmsg) ? "selected" : ""),
1714                                 oper,
1715                                 WC->msgarr[b],
1716                                 maxmsgs,
1717                                 is_summary,
1718                                 lo, hi);
1719                 }
1720                 wprintf("<option value=\"/%s?startmsg=%ld"
1721                         "?maxmsgs=9999999?summary=%d\">"
1722                         "ALL"
1723                         "</option> ",
1724                         oper,
1725                         WC->msgarr[0], is_summary);
1726
1727                 wprintf("</select> of %d messages.", nummsgs);
1728                 wprintf("</form>\n");
1729             }
1730         }
1731
1732 DONE:
1733         if (is_tasks) {
1734                 do_tasks_view();        /* Render the task list */
1735         }
1736
1737         if (is_calendar) {
1738                 do_calendar_view();     /* Render the calendar */
1739         }
1740
1741         if (is_addressbook) {
1742                 do_addrbook_view(addrbook, num_ab);     /* Render the address book */
1743         }
1744
1745         /* Put the data transfer hidden iframe in a hidden div, to make it *really* hidden */
1746         wprintf("</div>"
1747                 "<div display=\"hidden\">\n"
1748                 "<iframe name=\"msgloader1\" id=\"msgloader1\" width=\"1\"></iframe>\n"
1749         );
1750
1751         /* Note: wDumpContent() will output one additional </div> tag. */
1752         wDumpContent(1);
1753         if (addrbook != NULL) free(addrbook);
1754
1755         /* free the summary */
1756         if (WC->num_summ != 0) {
1757                 WC->num_summ = 0;
1758                 free(WC->summ);
1759         }
1760
1761         /* If we got here via a mailbox view and are reading a single
1762          * message, mark it as "seen." We do this after rendering the web page
1763          * so it doesn't keep the user waiting.
1764          */
1765         if ( (maxmsgs == 1) && (WC->wc_view == VIEW_MAILBOX) ) {
1766                 serv_printf("SEEN %ld|1", startmsg);
1767                 serv_getln(buf, sizeof buf);
1768         }
1769 }
1770
1771
1772 /*
1773  * Back end for post_message() ... this is where the actual message
1774  * gets transmitted to the server.
1775  */
1776 void post_mime_to_server(void) {
1777         char boundary[SIZ];
1778         int is_multipart = 0;
1779         static int seq = 0;
1780         struct wc_attachment *att;
1781         char *encoded;
1782         size_t encoded_length;
1783
1784         /* If there are attachments, we have to do multipart/mixed */
1785         if (WC->first_attachment != NULL) {
1786                 is_multipart = 1;
1787         }
1788
1789         if (is_multipart) {
1790                 sprintf(boundary, "---Citadel-Multipart-%s-%04x%04x---",
1791                         serv_info.serv_fqdn,
1792                         getpid(),
1793                         ++seq
1794                 );
1795
1796                 /* Remember, serv_printf() appends an extra newline */
1797                 serv_printf("Content-type: multipart/mixed; "
1798                         "boundary=\"%s\"\n", boundary);
1799                 serv_printf("This is a multipart message in MIME format.\n");
1800                 serv_printf("--%s", boundary);
1801         }
1802
1803         serv_puts("Content-type: text/html; charset=utf-8");
1804         serv_puts("");
1805         serv_puts("<HTML><BODY>\n");
1806         text_to_server(bstr("msgtext"), 0);
1807         serv_puts("</BODY></HTML>\n");
1808         
1809
1810         if (is_multipart) {
1811
1812                 /* Add in the attachments */
1813                 for (att = WC->first_attachment; att!=NULL; att=att->next) {
1814
1815                         encoded_length = ((att->length * 150) / 100);
1816                         encoded = malloc(encoded_length);
1817                         if (encoded == NULL) break;
1818                         CtdlEncodeBase64(encoded, att->data, att->length);
1819
1820                         serv_printf("--%s", boundary);
1821                         serv_printf("Content-type: %s", att->content_type);
1822                         serv_printf("Content-disposition: attachment; "
1823                                 "filename=\"%s\"", att->filename);
1824                         serv_puts("Content-transfer-encoding: base64");
1825                         serv_puts("");
1826                         serv_write(encoded, strlen(encoded));
1827                         serv_puts("");
1828                         serv_puts("");
1829                         free(encoded);
1830                 }
1831                 serv_printf("--%s--", boundary);
1832         }
1833
1834         serv_puts("000");
1835 }
1836
1837
1838 /*
1839  * Post message (or don't post message)
1840  *
1841  * Note regarding the "dont_post" variable:
1842  * A random value (actually, it's just a timestamp) is inserted as a hidden
1843  * field called "postseq" when the display_enter page is generated.  This
1844  * value is checked when posting, using the static variable dont_post.  If a
1845  * user attempts to post twice using the same dont_post value, the message is
1846  * discarded.  This prevents the accidental double-saving of the same message
1847  * if the user happens to click the browser "back" button.
1848  */
1849 void post_message(void)
1850 {
1851         char buf[SIZ];
1852         static long dont_post = (-1L);
1853         struct wc_attachment *att, *aptr;
1854
1855         if (WC->upload_length > 0) {
1856
1857                 /* There's an attachment.  Save it to this struct... */
1858                 att = malloc(sizeof(struct wc_attachment));
1859                 memset(att, 0, sizeof(struct wc_attachment));
1860                 att->length = WC->upload_length;
1861                 strcpy(att->content_type, WC->upload_content_type);
1862                 strcpy(att->filename, WC->upload_filename);
1863                 att->next = NULL;
1864
1865                 /* And add it to the list. */
1866                 if (WC->first_attachment == NULL) {
1867                         WC->first_attachment = att;
1868                 }
1869                 else {
1870                         aptr = WC->first_attachment;
1871                         while (aptr->next != NULL) aptr = aptr->next;
1872                         aptr->next = att;
1873                 }
1874
1875                 /* Netscape sends a simple filename, which is what we want,
1876                  * but Satan's browser sends an entire pathname.  Reduce
1877                  * the path to just a filename if we need to.
1878                  */
1879                 while (num_tokens(att->filename, '/') > 1) {
1880                         remove_token(att->filename, 0, '/');
1881                 }
1882                 while (num_tokens(att->filename, '\\') > 1) {
1883                         remove_token(att->filename, 0, '\\');
1884                 }
1885
1886                 /* Transfer control of this memory from the upload struct
1887                  * to the attachment struct.
1888                  */
1889                 att->data = WC->upload;
1890                 WC->upload_length = 0;
1891                 WC->upload = NULL;
1892                 display_enter();
1893                 return;
1894         }
1895
1896         if (!strcasecmp(bstr("sc"), "Cancel")) {
1897                 sprintf(WC->ImportantMessage, 
1898                         "Cancelled.  Message was not posted.");
1899         } else if (!strcasecmp(bstr("attach"), "Add")) {
1900                 display_enter();
1901                 return;
1902         } else if (atol(bstr("postseq")) == dont_post) {
1903                 sprintf(WC->ImportantMessage, 
1904                         "Automatically cancelled because you have already "
1905                         "saved this message.");
1906         } else {
1907                 sprintf(buf, "ENT0 1|%s|0|4|%s",
1908                         bstr("recp"),
1909                         bstr("subject") );
1910                 serv_puts(buf);
1911                 serv_getln(buf, sizeof buf);
1912                 if (buf[0] == '4') {
1913                         post_mime_to_server();
1914                         if (strlen(bstr("recp")) > 0) {
1915                                 sprintf(WC->ImportantMessage, "Message has been sent.\n");
1916                         }
1917                         else {
1918                                 sprintf(WC->ImportantMessage, "Message has been posted.\n");
1919                         }
1920                         dont_post = atol(bstr("postseq"));
1921                 } else {
1922                         sprintf(WC->ImportantMessage, 
1923                                 "%s", &buf[4]);
1924                 }
1925         }
1926
1927         free_attachments(WC);
1928         readloop("readnew");
1929 }
1930
1931
1932
1933
1934 /*
1935  * display the message entry screen
1936  */
1937 void display_enter(void)
1938 {
1939         char buf[SIZ];
1940         long now;
1941         struct wc_attachment *att;
1942
1943         if (strlen(bstr("force_room")) > 0) {
1944                 gotoroom(bstr("force_room"));
1945         }
1946
1947         /* Are we perhaps in an address book view?  If so, then an "enter
1948          * message" command really means "add new entry."
1949          */
1950         if (WC->wc_view == VIEW_ADDRESSBOOK) {
1951                 do_edit_vcard(-1, "", "");
1952                 return;
1953         }
1954
1955 #ifdef WEBCIT_WITH_CALENDAR_SERVICE
1956         /* Are we perhaps in a calendar view?  If so, then an "enter
1957          * message" command really means "add new calendar item."
1958          */
1959         if (WC->wc_view == VIEW_CALENDAR) {
1960                 display_edit_event();
1961                 return;
1962         }
1963
1964         /* Are we perhaps in a tasks view?  If so, then an "enter
1965          * message" command really means "add new task."
1966          */
1967         if (WC->wc_view == VIEW_TASKS) {
1968                 display_edit_task();
1969                 return;
1970         }
1971 #endif
1972
1973         /*
1974          * Otherwise proceed normally.
1975 `        * Do a custom room banner with no navbar...
1976          */
1977         output_headers(1, 1, 2, 0, 0, 0, 0);
1978         wprintf("<div id=\"banner\">\n");
1979         embed_room_banner(NULL, navbar_none);
1980         wprintf("</div>\n");
1981         wprintf("<div id=\"content\">\n"
1982                 "<div id=\"fix_scrollbar_bug\">"
1983                 "<table width=100%% border=0 bgcolor=\"#ffffff\"><tr><td>");
1984
1985         sprintf(buf, "ENT0 0|%s|0|0", bstr("recp"));
1986         serv_puts(buf);
1987         serv_getln(buf, sizeof buf);
1988
1989         if (!strncmp(buf, "570", 3)) {
1990                 if (strlen(bstr("recp")) > 0) {
1991                         svprintf("RECPERROR", WCS_STRING,
1992                                 "<SPAN CLASS=\"errormsg\">%s</SPAN><br />\n",
1993                                 &buf[4]
1994                         );
1995                 }
1996                 do_template("prompt_for_recipient");
1997                 goto DONE;
1998         }
1999         if (buf[0] != '2') {
2000                 wprintf("<EM>%s</EM><br />\n", &buf[4]);
2001                 goto DONE;
2002         }
2003
2004         now = time(NULL);
2005         fmt_date(buf, now, 0);
2006         strcat(&buf[strlen(buf)], " <I>from</I> ");
2007         stresc(&buf[strlen(buf)], WC->wc_username, 1, 1);
2008         if (strlen(bstr("recp")) > 0) {
2009                 strcat(&buf[strlen(buf)], " <I>to</I> ");
2010                 stresc(&buf[strlen(buf)], bstr("recp"), 1, 1);
2011         }
2012         strcat(&buf[strlen(buf)], " <I>in</I> ");
2013         stresc(&buf[strlen(buf)], WC->wc_roomname, 1, 1);
2014
2015         /* begin message entry screen */
2016         // wprintf("<div style=\"position:absolute; left:1%%; width:96%%; height:100%%\">\n");
2017
2018         wprintf("<form enctype=\"multipart/form-data\" "
2019                 "method=\"POST\" action=\"/post\" "
2020                 "name=\"enterform\""
2021                 "onSubmit=\"return submitForm();\""
2022                 ">\n");
2023         wprintf("<input type=\"hidden\" name=\"recp\" value=\"%s\">\n",
2024                 bstr("recp"));
2025         wprintf("<input type=\"hidden\" name=\"postseq\" value=\"%ld\">\n",
2026                 now);
2027
2028         wprintf("%s<br>\n", buf);       /* header bar */
2029         wprintf("<img src=\"static/newmess3_24x.gif\" align=middle alt=\" \">");
2030                 /* "onLoad=\"document.enterform.msgtext.focus();\" " */
2031         wprintf("<font size=-1>Subject (optional):</font>"
2032                 "<input type=\"text\" name=\"subject\" value=\"");
2033         escputs(bstr("subject"));
2034         wprintf("\" size=40 maxlength=70>"
2035                 "&nbsp;"
2036         );
2037
2038         wprintf("<input type=\"submit\" name=\"sc\" value=\"");
2039         if (strlen(bstr("recp")) > 0) {
2040                 wprintf("Send message");
2041         } else {
2042                 wprintf("Post message");
2043         }
2044         wprintf("\">&nbsp;"
2045                 "<input type=\"submit\" name=\"sc\" value=\"Cancel\">\n");
2046
2047         wprintf("<center><script type=\"text/javascript\" "
2048                 "src=\"static/richtext.js\"></script>\n"
2049                 "<script type=\"text/javascript\">\n"
2050                 "function submitForm() { \n"
2051                 "  updateRTE('msgtext'); \n"
2052                 "  return true; \n"
2053                 "} \n"
2054                 "  \n"
2055                 "initRTE(\"static/\", \"static/\", \"\"); \n"
2056                 "</script> \n"
2057                 "<noscript>JavaScript must be enabled.</noscript> \n"
2058                 "<script type=\"text/javascript\"> \n"
2059                 "writeRichText('msgtext', '");
2060         msgescputs(bstr("msgtext"));
2061         wprintf("', '96%%', '200', true, false); \n"
2062                 "</script></center><br />\n");
2063
2064         /* Enumerate any attachments which are already in place... */
2065         wprintf("<img src=\"/static/diskette_24x.gif\" border=0 "
2066                 "align=middle height=16 width=16> Attachments: ");
2067         wprintf("<select name=\"which_attachment\" size=1>");
2068         for (att = WC->first_attachment; att != NULL; att = att->next) {
2069                 wprintf("<option value=\"");
2070                 urlescputs(att->filename);
2071                 wprintf("\">");
2072                 escputs(att->filename);
2073                 /* wprintf(" (%s, %d bytes)",att->content_type,att->length); */
2074                 wprintf("</option>\n");
2075         }
2076         wprintf("</select>");
2077
2078         /* Now offer the ability to attach additional files... */
2079         wprintf("&nbsp;&nbsp;&nbsp;"
2080                 "Attach file: <input NAME=\"attachfile\" "
2081                 "SIZE=16 TYPE=\"file\">\n&nbsp;&nbsp;"
2082                 "<input type=\"submit\" name=\"attach\" value=\"Add\">\n");
2083
2084         wprintf("</form>\n");
2085
2086         wprintf("</td></tr></table></div>\n");
2087 DONE:   wDumpContent(1);
2088 }
2089
2090
2091
2092
2093
2094
2095
2096
2097 void delete_msg(void)
2098 {
2099         long msgid;
2100         char buf[SIZ];
2101
2102         msgid = atol(bstr("msgid"));
2103
2104         output_headers(1, 1, 1, 0, 0, 0, 0);
2105
2106         sprintf(buf, "DELE %ld", msgid);
2107         serv_puts(buf);
2108         serv_getln(buf, sizeof buf);
2109         wprintf("<EM>%s</EM><br />\n", &buf[4]);
2110
2111         wDumpContent(1);
2112 }
2113
2114
2115
2116
2117 /*
2118  * Confirm move of a message
2119  */
2120 void confirm_move_msg(void)
2121 {
2122         long msgid;
2123         char buf[SIZ];
2124         char targ[SIZ];
2125
2126         msgid = atol(bstr("msgid"));
2127
2128         output_headers(1, 1, 1, 0, 0, 0, 0);
2129
2130         wprintf("<div id=\"fix_scrollbar_bug\">"
2131                 "<table width=100%% border=0 bgcolor=\"#444455\"><tr><td>");
2132         wprintf("<font size=+1 color=\"#ffffff\"");
2133         wprintf("<b>Confirm move of message</b>\n");
2134         wprintf("</font></td></tr></table></div>\n");
2135
2136         wprintf("<CENTER>");
2137
2138         wprintf("Move this message to:<br />\n");
2139
2140         wprintf("<form METHOD=\"POST\" ACTION=\"/move_msg\">\n");
2141         wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgid\" VALUE=\"%s\">\n",
2142                 bstr("msgid"));
2143
2144
2145         wprintf("<SELECT NAME=\"target_room\" SIZE=5>\n");
2146         serv_puts("LKRA");
2147         serv_getln(buf, sizeof buf);
2148         if (buf[0] == '1') {
2149                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
2150                         extract_token(targ, buf, 0, '|', sizeof targ);
2151                         wprintf("<OPTION>");
2152                         escputs(targ);
2153                         wprintf("\n");
2154                 }
2155         }
2156         wprintf("</SELECT>\n");
2157         wprintf("<br />\n");
2158
2159         wprintf("<INPUT TYPE=\"submit\" NAME=\"yesno\" VALUE=\"Move\">");
2160         wprintf("&nbsp;");
2161         wprintf("<INPUT TYPE=\"submit\" NAME=\"yesno\" VALUE=\"Cancel\">");
2162         wprintf("</form></CENTER>\n");
2163
2164         wprintf("</CENTER>\n");
2165         wDumpContent(1);
2166 }
2167
2168
2169
2170 void move_msg(void)
2171 {
2172         long msgid;
2173         char buf[SIZ];
2174
2175         msgid = atol(bstr("msgid"));
2176
2177         output_headers(1, 1, 1, 0, 0, 0, 0);
2178
2179         if (!strcasecmp(bstr("yesno"), "Move")) {
2180                 sprintf(buf, "MOVE %ld|%s", msgid, bstr("target_room"));
2181                 serv_puts(buf);
2182                 serv_getln(buf, sizeof buf);
2183                 wprintf("<EM>%s</EM><br />\n", &buf[4]);
2184         } else {
2185                 wprintf("<EM>Message not moved.</EM><br />\n");
2186         }
2187
2188         wDumpContent(1);
2189 }
2190
2191 /*
2192  * This gets called when a user selects multiple messages in a summary
2193  * list and then clicks to perform a transformation of some sort on them
2194  * (such as deleting them).
2195  */
2196 void do_stuff_to_msgs(void) {
2197         char buf[SIZ];
2198         char sc[SIZ];
2199
2200         struct stuff_t {
2201                 struct stuff_t *next;
2202                 long msgnum;
2203         };
2204
2205         struct stuff_t *stuff = NULL;
2206         struct stuff_t *ptr;
2207
2208
2209         serv_puts("MSGS ALL");
2210         serv_getln(buf, sizeof buf);
2211
2212         if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
2213                 ptr = malloc(sizeof(struct stuff_t));
2214                 ptr->msgnum = atol(buf);
2215                 ptr->next = stuff;
2216                 stuff = ptr;
2217         }
2218
2219         strcpy(sc, bstr("sc"));
2220
2221         while (stuff != NULL) {
2222
2223                 sprintf(buf, "msg_%ld", stuff->msgnum);
2224                 if (!strcasecmp(bstr(buf), "yes")) {
2225
2226                         if (!strcasecmp(sc, "Delete")) {
2227                                 serv_printf("DELE %ld", stuff->msgnum);
2228                                 serv_getln(buf, sizeof buf);
2229                         }
2230
2231                 }
2232
2233                 ptr = stuff->next;
2234                 free(stuff);
2235                 stuff = ptr;
2236         }
2237
2238         readloop("readfwd");
2239 }