* change syntax of read_message
[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 "webserver.h"
10 #include "groupdav.h"
11
12 HashList *MsgHeaderHandler = NULL;
13 HashList *MsgEvaluators = NULL;
14 HashList *MimeRenderHandler = NULL;
15
16 #define SUBJ_COL_WIDTH_PCT              50      /**< Mailbox view column width */
17 #define SENDER_COL_WIDTH_PCT            30      /**< Mailbox view column width */
18 #define DATE_PLUS_BUTTONS_WIDTH_PCT     20      /**< Mailbox view column width */
19
20
21
22 void display_enter(void);
23 int longcmp_r(const void *s1, const void *s2);
24 int summcmp_subj(const void *s1, const void *s2);
25 int summcmp_rsubj(const void *s1, const void *s2);
26 int summcmp_sender(const void *s1, const void *s2);
27 int summcmp_rsender(const void *s1, const void *s2);
28 int summcmp_date(const void *s1, const void *s2);
29 int summcmp_rdate(const void *s1, const void *s2);
30
31 /*----------------------------------------------------------------------------*/
32
33
34 typedef void (*MsgPartEvaluatorFunc)(message_summary *Sum, StrBuf *Buf);
35
36
37
38 const char* SortIcons[3] = {
39         "static/up_pointer.gif",
40         "static/down_pointer.gif",
41         "static/sort_none.gif"
42 };
43
44 enum  {/// SortByEnum
45         eDate,
46         eRDate,
47         eSubject,
48         eRSubject,
49         eSender,
50         eRSender,
51         eReverse,
52         eUnSet
53 }; 
54
55 /* SortEnum to plain string representation */
56 static const char* SortByStrings[] = {
57         "date",
58         "rdate",
59         "subject", 
60         "rsubject", 
61         "sender",
62         "rsender",
63         "reverse",
64         "unset"
65 };
66
67 /* SortEnum to sort-Function Table */
68 const CompareFunc SortFuncs[eUnSet] = {
69         summcmp_date,
70         summcmp_rdate,
71         summcmp_subj,
72         summcmp_rsubj,
73         summcmp_sender,
74         summcmp_rsender,
75         summcmp_rdate
76 };
77
78 /* given a SortEnum, which icon should we choose? */
79 const int SortDateToIcon[eUnSet] = { eUp, eDown, eNone, eNone, eNone, eNone, eNone};
80 const int SortSubjectToIcon[eUnSet] = { eNone, eNone, eUp, eDown, eNone, eNone, eNone};
81 const int SortSenderToIcon[eUnSet] = { eNone, eNone, eNone, eNone, eUp, eDown, eNone};
82
83 /* given a SortEnum, which would be the "opposite" search option? */
84 const int DateInvertSortString[eUnSet] =  { eRDate, eDate, eDate, eDate, eDate, eDate, eDate};
85 const int SubjectInvertSortString[eUnSet] =  { eSubject, eSubject, eRSubject, eUnSet, eSubject, eSubject, eSubject};
86 const int SenderInvertSortString[eUnSet] =  { eSender, eSender, eSender, eSender, eRSender, eUnSet, eSender};
87
88
89
90 /*----------------------------------------------------------------------------*/
91
92 /*
93  * Translates sortoption String to its SortEnum representation 
94  * returns the enum matching the string; defaults to RDate
95  */
96 //SortByEnum 
97 int StrToESort (const StrBuf *sortby)
98 {////todoo: hash
99         int result = eDate;
100
101         if (!IsEmptyStr(ChrPtr(sortby))) while (result < eUnSet){
102                         if (!strcasecmp(ChrPtr(sortby), 
103                                         SortByStrings[result])) 
104                                 return result;
105                         result ++;
106                 }
107         return eRDate;
108 }
109
110
111 typedef int (*QSortFunction) (const void*, const void*);
112
113 /*
114  * qsort() compatible function to compare two longs in descending order.
115  */
116 int longcmp_r(const void *s1, const void *s2) {
117         long l1;
118         long l2;
119
120         l1 = *(long *)GetSearchPayload(s1);
121         l2 = *(long *)GetSearchPayload(s2);
122
123         if (l1 > l2) return(-1);
124         if (l1 < l2) return(+1);
125         return(0);
126 }
127
128 /*
129  * qsort() compatible function to compare two longs in descending order.
130  */
131 int qlongcmp_r(const void *s1, const void *s2) {
132         long l1 = (long) s1;
133         long l2 = (long) s2;
134
135         if (l1 > l2) return(-1);
136         if (l1 < l2) return(+1);
137         return(0);
138 }
139
140  
141 /*
142  * qsort() compatible function to compare two message summary structs by ascending subject.
143  */
144 int summcmp_subj(const void *s1, const void *s2) {
145         message_summary *summ1;
146         message_summary *summ2;
147         
148         summ1 = (message_summary *)GetSearchPayload(s1);
149         summ2 = (message_summary *)GetSearchPayload(s2);
150         return strcasecmp(ChrPtr(summ1->subj), ChrPtr(summ2->subj));
151 }
152
153 /*
154  * qsort() compatible function to compare two message summary structs by descending subject.
155  */
156 int summcmp_rsubj(const void *s1, const void *s2) {
157         message_summary *summ1;
158         message_summary *summ2;
159         
160         summ1 = (message_summary *)GetSearchPayload(s1);
161         summ2 = (message_summary *)GetSearchPayload(s2);
162         return strcasecmp(ChrPtr(summ2->subj), ChrPtr(summ1->subj));
163 }
164
165 /*
166  * qsort() compatible function to compare two message summary structs by ascending sender.
167  */
168 int summcmp_sender(const void *s1, const void *s2) {
169         message_summary *summ1;
170         message_summary *summ2;
171         
172         summ1 = (message_summary *)GetSearchPayload(s1);
173         summ2 = (message_summary *)GetSearchPayload(s2);
174         return strcasecmp(ChrPtr(summ1->from), ChrPtr(summ2->from));
175 }
176
177 /*
178  * qsort() compatible function to compare two message summary structs by descending sender.
179  */
180 int summcmp_rsender(const void *s1, const void *s2) {
181         message_summary *summ1;
182         message_summary *summ2;
183         
184         summ1 = (message_summary *)GetSearchPayload(s1);
185         summ2 = (message_summary *)GetSearchPayload(s2);
186         return strcasecmp(ChrPtr(summ2->from), ChrPtr(summ1->from));
187 }
188
189 /*
190  * qsort() compatible function to compare two message summary structs by ascending date.
191  */
192 int summcmp_date(const void *s1, const void *s2) {
193         message_summary *summ1;
194         message_summary *summ2;
195         
196         summ1 = (message_summary *)GetSearchPayload(s1);
197         summ2 = (message_summary *)GetSearchPayload(s2);
198
199         if (summ1->date < summ2->date) return -1;
200         else if (summ1->date > summ2->date) return +1;
201         else return 0;
202 }
203
204 /*
205  * qsort() compatible function to compare two message summary structs by descending date.
206  */
207 int summcmp_rdate(const void *s1, const void *s2) {
208         message_summary *summ1;
209         message_summary *summ2;
210         
211         summ1 = (message_summary *)GetSearchPayload(s1);
212         summ2 = (message_summary *)GetSearchPayload(s2);
213
214         if (summ1->date < summ2->date) return +1;
215         else if (summ1->date > summ2->date) return -1;
216         else return 0;
217 }
218
219
220
221
222
223
224 /*
225  * I wanna SEE that message!
226  *
227  * msgnum               Message number to display
228  * printable_view       Nonzero to display a printable view
229  * section              Optional for encapsulated message/rfc822 submessage
230  */
231 void read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, int printable_view, const StrBuf *section) {
232         StrBuf *Buf;
233         StrBuf *Token;
234         StrBuf *FoundCharset;
235         message_summary *Msg = NULL;
236         headereval *Hdr;
237         void *vHdr;
238         char buf[SIZ];
239 //      char mime_submessages[256] = "";
240         char reply_references[1024] = "";
241         int Done = 0;
242         int state=0;
243         
244
245         Buf = NewStrBuf();
246         lprintf(1, "-------------------MSG4 %ld|%s--------------\n", msgnum, ChrPtr(section));
247         serv_printf("MSG4 %ld|%s", msgnum, ChrPtr(section));
248         StrBuf_ServGetln(Buf);
249         if (GetServerStatus(Buf, NULL) != 1) {
250                 StrBufAppendPrintf(Target, "<strong>");
251                 StrBufAppendPrintf(Target, _("ERROR:"));
252                 StrBufAppendPrintf(Target, "</strong> %s<br />\n", &buf[4]);
253                 FreeStrBuf(&Buf);
254                 return;
255         }
256         svputlong("MsgPrintable", printable_view);
257         /** begin everythingamundo table */
258
259
260         Token = NewStrBuf();
261         Msg = (message_summary *)malloc(sizeof(message_summary));
262         memset(Msg, 0, sizeof(message_summary));
263         Msg->msgnum = msgnum;
264         FoundCharset = NewStrBuf();
265         while ((StrBuf_ServGetln(Buf)>=0) && !Done) {
266                 if ( (StrLength(Buf)==3) && 
267                     !strcmp(ChrPtr(Buf), "000")) 
268                 {
269                         Done = 1;
270                         if (state < 2) {
271                                 StrBufAppendPrintf(Target, "<i>");
272                                 StrBufAppendPrintf(Target, _("unexpected end of message"));
273                                 StrBufAppendPrintf(Target, " (1)</i><br /><br />\n");
274                                 StrBufAppendPrintf(Target, "</div>\n");
275                                 FreeStrBuf(&Buf);
276                                 FreeStrBuf(&Token);
277                                 DestroyMessageSummary(Msg);
278                                 FreeStrBuf(&FoundCharset);
279                                 return;
280                         }
281                         else {
282                                 break;
283                         }
284                 }
285                 switch (state) {
286                 case 0:/* Citadel Message Headers */
287                         if (StrLength(Buf) == 0) {
288                                 state ++;
289                                 break;
290                         }
291                         StrBufExtract_token(Token, Buf, 0, '=');
292                         StrBufCutLeft(Buf, StrLength(Token) + 1);
293                         
294                         lprintf(1, ":: [%s] = [%s]\n", ChrPtr(Token), ChrPtr(Buf));
295                         if (GetHash(MsgHeaderHandler, SKEY(Token), &vHdr) &&
296                             (vHdr != NULL)) {
297                                 Hdr = (headereval*)vHdr;
298                                 Hdr->evaluator(Msg, Buf, FoundCharset);
299                                 if (Hdr->Type == 1) {
300                                         state++;
301                                 }
302                         }
303                         else lprintf(1, "don't know how to handle message header[%s]\n", ChrPtr(Token));
304                         break;
305                 case 1:/* Message Mime Header */
306                         if (StrLength(Buf) == 0) {
307                                 state++;
308                                 if (Msg->MsgBody.ContentType == NULL)
309                                         /* end of header or no header? */
310                                         Msg->MsgBody.ContentType = NewStrBufPlain(HKEY("text/plain"));
311                                  /* usual end of mime header */
312                         }
313                         else
314                         {
315                                 StrBufExtract_token(Token, Buf, 0, ':');
316                                 if (StrLength(Token) > 0) {
317                                         StrBufCutLeft(Buf, StrLength(Token) + 1);
318                                         lprintf(1, ":: [%s] = [%s]\n", ChrPtr(Token), ChrPtr(Buf));
319                                         if (GetHash(MsgHeaderHandler, SKEY(Token), &vHdr) &&
320                                             (vHdr != NULL)) {
321                                                 Hdr = (headereval*)vHdr;
322                                                 Hdr->evaluator(Msg, Buf, FoundCharset);
323                                         }
324                                         break;
325                                 }
326                         }
327                 case 2: /* Message Body */
328                         
329                         if (Msg->MsgBody.size_known > 0) {
330                                 StrBuf_ServGetBLOB(Msg->MsgBody.Data, Msg->MsgBody.length);
331                                 state ++;
332                                         /// todo: check next line, if not 000, append following lines
333                         }
334                         else if (1){
335                                 if (StrLength(Msg->MsgBody.Data) > 0)
336                                         StrBufAppendBufPlain(Msg->MsgBody.Data, "\n", 1, 0);
337                                 StrBufAppendBuf(Msg->MsgBody.Data, Buf, 0);
338                         }
339                         break;
340                 case 3:
341                         StrBufAppendBuf(Msg->MsgBody.Data, Buf, 0);
342                         break;
343                 }
344         }
345         
346         /* strip the bare contenttype, so we ommit charset etc. */
347         StrBufExtract_token(Buf, Msg->MsgBody.ContentType, 0, ';');
348         StrBufTrim(Buf);
349         if (GetHash(MimeRenderHandler, SKEY(Buf), &vHdr) &&
350             (vHdr != NULL)) {
351                 RenderMimeFunc Render;
352                 Render = (RenderMimeFunc)vHdr;
353                 Render(&Msg->MsgBody, NULL, FoundCharset);
354         }
355
356
357         if (StrLength(Msg->reply_references)> 0) {
358                 /* Trim down excessively long lists of thread references.  We eliminate the
359                  * second one in the list so that the thread root remains intact.
360                  */
361                 int rrtok = num_tokens(ChrPtr(Msg->reply_references), '|');
362                 int rrlen = StrLength(Msg->reply_references);
363                 if ( ((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10) ) {
364                         remove_token(reply_references, 1, '|');////todo
365                 }
366         }
367
368         /* Generate a reply-to address */
369         if (StrLength(Msg->Rfca) > 0) {
370                 if (Msg->reply_to == NULL)
371                         Msg->reply_to = NewStrBuf();
372                 if (StrLength(Msg->from) > 0) {
373                         StrBufPrintf(Msg->reply_to, "%s <%s>", ChrPtr(Msg->from), ChrPtr(Msg->Rfca));
374                 }
375                 else {
376                         FlushStrBuf(Msg->reply_to);
377                         StrBufAppendBuf(Msg->reply_to, Msg->Rfca, 0);
378                 }
379         }
380         else 
381         {
382                 if ((StrLength(Msg->OtherNode)>0) && 
383                     (strcasecmp(ChrPtr(Msg->OtherNode), serv_info.serv_nodename)) &&
384                     (strcasecmp(ChrPtr(Msg->OtherNode), serv_info.serv_humannode)) ) 
385                 {
386                         if (Msg->reply_to == NULL)
387                                 Msg->reply_to = NewStrBuf();
388                         StrBufPrintf(Msg->reply_to, 
389                                      "%s @ %s",
390                                      ChrPtr(Msg->from), 
391                                      ChrPtr(Msg->OtherNode));
392                 }
393                 else {
394                         if (Msg->reply_to == NULL)
395                                 Msg->reply_to = NewStrBuf();
396                         FlushStrBuf(Msg->reply_to);
397                         StrBufAppendBuf(Msg->reply_to, Msg->from, 0);
398                 }
399         }
400         DoTemplate(tmpl, tmpllen, Target, Msg, CTX_MAILSUM);
401
402         DestroyMessageSummary(Msg);
403         FreeStrBuf(&FoundCharset);
404         FreeStrBuf(&Token);
405         FreeStrBuf(&Buf);
406 }
407
408
409
410 /*
411  * Unadorned HTML output of an individual message, suitable
412  * for placing in a hidden iframe, for printing, or whatever
413  *
414  * msgnum_as_string == Message number, as a string instead of as a long int
415  */
416 void embed_message(void) {
417         long msgnum = 0L;
418         const StrBuf *Tmpl = sbstr("template");
419
420         msgnum = StrTol(WC->UrlFragment1);
421         if (StrLength(Tmpl) > 0) 
422                 read_message(WC->WBuf, SKEY(Tmpl), msgnum, 0, NULL);
423         else 
424                 read_message(WC->WBuf, HKEY("view_message"), msgnum, 0, NULL);
425 }
426
427
428 /*
429  * Printable view of a message
430  *
431  * msgnum_as_string == Message number, as a string instead of as a long int
432  */
433 void print_message(void) {
434         long msgnum = 0L;
435
436         msgnum = StrTol(WC->UrlFragment1);
437         output_headers(0, 0, 0, 0, 0, 0);
438
439         hprintf("Content-type: text/html\r\n"
440                 "Server: " PACKAGE_STRING "\r\n"
441                 "Connection: close\r\n");
442
443         begin_burst();
444
445         read_message(WC->WBuf, HKEY("view_message_print"), msgnum, 1, NULL);
446
447         wDumpContent(0);
448 }
449
450 /* 
451  * Mobile browser view of message
452  *
453  * @param msg_num_as_string Message number as a string instead of as a long int 
454  */
455 void mobile_message_view(void) {
456   long msgnum = 0L;
457   msgnum = StrTol(WC->UrlFragment1);
458   output_headers(1, 0, 0, 0, 0, 1);
459   begin_burst();
460   do_template("msgcontrols", NULL);
461   read_message(WC->WBuf, HKEY("view_message"), msgnum,1, NULL);
462   wDumpContent(0);
463 }
464
465 /**
466  * \brief Display a message's headers
467  *
468  * \param msgnum_as_string Message number, as a string instead of as a long int
469  */
470 void display_headers(void) {
471         long msgnum = 0L;
472         char buf[1024];
473
474         msgnum = StrTol(WC->UrlFragment1);
475         output_headers(0, 0, 0, 0, 0, 0);
476
477         hprintf("Content-type: text/plain\r\n"
478                 "Server: %s\r\n"
479                 "Connection: close\r\n",
480                 PACKAGE_STRING);
481         begin_burst();
482
483         serv_printf("MSG2 %ld|3", msgnum);
484         serv_getln(buf, sizeof buf);
485         if (buf[0] == '1') {
486                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
487                         wprintf("%s\n", buf);
488                 }
489         }
490
491         wDumpContent(0);
492 }
493
494
495
496 /**
497  * \brief Read message in simple, JavaScript-embeddable form for 'forward'
498  *      or 'reply quoted' operations.
499  *
500  * NOTE: it is VITALLY IMPORTANT that we output no single-quotes or linebreaks
501  *       in this function.  Doing so would throw a JavaScript error in the
502  *       'supplied text' argument to the editor.
503  *
504  * \param msgnum Message number of the message we want to quote
505  * \param forward_attachments Nonzero if we want attachments to be forwarded
506  */
507 void pullquote_message(long msgnum, int forward_attachments, int include_headers) {
508         struct wcsession *WCC = WC;
509         char buf[SIZ];
510         char mime_partnum[256];
511         char mime_filename[256];
512         char mime_content_type[256];
513         char mime_charset[256];
514         char mime_disposition[256];
515         int mime_length;
516         char *attachments = NULL;
517         char *ptr = NULL;
518         int num_attachments = 0;
519         wc_attachment *att;
520         char m_subject[1024];
521         char from[256];
522         char node[256];
523         char rfca[256];
524         char to[256];
525         char reply_to[512];
526         char now[256];
527         int format_type = 0;
528         int nhdr = 0;
529         int bq = 0;
530         int i = 0;
531 #ifdef HAVE_ICONV
532         iconv_t ic = (iconv_t)(-1) ;
533         char *ibuf;                /**< Buffer of characters to be converted */
534         char *obuf;                /**< Buffer for converted characters      */
535         size_t ibuflen;    /**< Length of input buffer         */
536         size_t obuflen;    /**< Length of output buffer       */
537         char *osav;                /**< Saved pointer to output buffer       */
538 #endif
539
540         strcpy(from, "");
541         strcpy(node, "");
542         strcpy(rfca, "");
543         strcpy(reply_to, "");
544         strcpy(mime_content_type, "text/plain");
545         strcpy(mime_charset, "us-ascii");
546
547         serv_printf("MSG4 %ld", msgnum);
548         serv_getln(buf, sizeof buf);
549         if (buf[0] != '1') {
550                 wprintf(_("ERROR:"));
551                 wprintf("%s<br />", &buf[4]);
552                 return;
553         }
554
555         strcpy(m_subject, "");
556
557         while (serv_getln(buf, sizeof buf), strcasecmp(buf, "text")) {
558                 if (!strcmp(buf, "000")) {
559                         wprintf("%s (3)", _("unexpected end of message"));
560                         return;
561                 }
562                 if (include_headers) {
563                         if (!strncasecmp(buf, "nhdr=yes", 8))
564                                 nhdr = 1;
565                         if (nhdr == 1)
566                                 buf[0] = '_';
567                         if (!strncasecmp(buf, "type=", 5))
568                                 format_type = atoi(&buf[5]);
569                         if (!strncasecmp(buf, "from=", 5)) {
570                                 strcpy(from, &buf[5]);
571                                 wprintf(_("from "));
572                                 utf8ify_rfc822_string(from);
573                                 msgescputs(from);
574                         }
575                         if (!strncasecmp(buf, "subj=", 5)) {
576                                 strcpy(m_subject, &buf[5]);
577                         }
578                         if ((!strncasecmp(buf, "hnod=", 5))
579                             && (strcasecmp(&buf[5], serv_info.serv_humannode))) {
580                                 wprintf("(%s) ", &buf[5]);
581                         }
582                         if ((!strncasecmp(buf, "room=", 5))
583                             && (strcasecmp(&buf[5], WC->wc_roomname))
584                             && (!IsEmptyStr(&buf[5])) ) {
585                                 wprintf(_("in "));
586                                 wprintf("%s&gt; ", &buf[5]);
587                         }
588                         if (!strncasecmp(buf, "rfca=", 5)) {
589                                 strcpy(rfca, &buf[5]);
590                                 wprintf("&lt;");
591                                 msgescputs(rfca);
592                                 wprintf("&gt; ");
593                         }
594                         if (!strncasecmp(buf, "node=", 5)) {
595                                 strcpy(node, &buf[5]);
596                                 if ( ((WC->room_flags & QR_NETWORK)
597                                 || ((strcasecmp(&buf[5], serv_info.serv_nodename)
598                                 && (strcasecmp(&buf[5], serv_info.serv_fqdn)))))
599                                 && (IsEmptyStr(rfca))
600                                 ) {
601                                         wprintf("@%s ", &buf[5]);
602                                 }
603                         }
604                         if (!strncasecmp(buf, "rcpt=", 5)) {
605                                 wprintf(_("to "));
606                                 strcpy(to, &buf[5]);
607                                 utf8ify_rfc822_string(to);
608                                 wprintf("%s ", to);
609                         }
610                         if (!strncasecmp(buf, "time=", 5)) {
611                                 webcit_fmt_date(now, atol(&buf[5]), 0);
612                                 wprintf("%s ", now);
613                         }
614                 }
615
616                 /**
617                  * Save attachment info for later.  We can't start downloading them
618                  * yet because we're in the middle of a server transaction.
619                  */
620                 if (!strncasecmp(buf, "part=", 5)) {
621                         ptr = malloc( (strlen(buf) + ((attachments != NULL) ? strlen(attachments) : 0)) ) ;
622                         if (ptr != NULL) {
623                                 ++num_attachments;
624                                 sprintf(ptr, "%s%s\n",
625                                         ((attachments != NULL) ? attachments : ""),
626                                         &buf[5]
627                                 );
628                                 free(attachments);
629                                 attachments = ptr;
630                                 lprintf(9, "attachments=<%s>\n", attachments);
631                         }
632                 }
633
634         }
635
636         if (include_headers) {
637                 wprintf("<br>");
638
639                 utf8ify_rfc822_string(m_subject);
640                 if (!IsEmptyStr(m_subject)) {
641                         wprintf(_("Subject:"));
642                         wprintf(" ");
643                         msgescputs(m_subject);
644                         wprintf("<br />");
645                 }
646
647                 /**
648                  * Begin body
649                  */
650                 wprintf("<br />");
651         }
652
653         /**
654          * Learn the content type
655          */
656         strcpy(mime_content_type, "text/plain");
657         while (serv_getln(buf, sizeof buf), (!IsEmptyStr(buf))) {
658                 if (!strcmp(buf, "000")) {
659                         wprintf("%s (4)", _("unexpected end of message"));
660                         goto ENDBODY;
661                 }
662                 if (!strncasecmp(buf, "Content-type: ", 14)) {
663                         int len;
664                         safestrncpy(mime_content_type, &buf[14],
665                                 sizeof(mime_content_type));
666                         for (i=0; i<strlen(mime_content_type); ++i) {
667                                 if (!strncasecmp(&mime_content_type[i], "charset=", 8)) {
668                                         safestrncpy(mime_charset, &mime_content_type[i+8],
669                                                 sizeof mime_charset);
670                                 }
671                         }
672                         len = strlen(mime_content_type);
673                         for (i=0; i<len; ++i) {
674                                 if (mime_content_type[i] == ';') {
675                                         mime_content_type[i] = 0;
676                                         len = i - 1;
677                                 }
678                         }
679                         len = strlen(mime_charset);
680                         for (i=0; i<len; ++i) {
681                                 if (mime_charset[i] == ';') {
682                                         mime_charset[i] = 0;
683                                         len = i - 1;
684                                 }
685                         }
686                 }
687         }
688
689         /** Set up a character set conversion if we need to (and if we can) */
690 #ifdef HAVE_ICONV
691         if ( (strcasecmp(mime_charset, "us-ascii"))
692            && (strcasecmp(mime_charset, "UTF-8"))
693            && (strcasecmp(mime_charset, ""))
694         ) {
695                 ctdl_iconv_open("UTF-8", mime_charset, &ic);
696                 if (ic == (iconv_t)(-1) ) {
697                         lprintf(5, "%s:%d iconv_open(%s, %s) failed: %s\n",
698                                 __FILE__, __LINE__, "UTF-8", mime_charset, strerror(errno));
699                 }
700         }
701 #endif
702
703         /** Messages in legacy Citadel variformat get handled thusly... */
704         if (!strcasecmp(mime_content_type, "text/x-citadel-variformat")) {
705                 pullquote_fmout();
706         }
707
708         /* Boring old 80-column fixed format text gets handled this way... */
709         else if (!strcasecmp(mime_content_type, "text/plain")) {
710                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
711                         int len;
712                         len = strlen(buf);
713                         if ((len > 0) && (buf[len-1] == '\n')) buf[--len] = 0;
714                         if ((len > 0) && (buf[len-1] == '\r')) buf[--len] = 0;
715
716 #ifdef HAVE_ICONV
717                         if (ic != (iconv_t)(-1) ) {
718                                 ibuf = buf;
719                                 ibuflen = len;
720                                 obuflen = SIZ;
721                                 obuf = (char *) malloc(obuflen);
722                                 osav = obuf;
723                                 iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
724                                 osav[SIZ-obuflen] = 0;
725                                 safestrncpy(buf, osav, sizeof buf);
726                                 free(osav);
727                         }
728 #endif
729
730                         len = strlen(buf);
731                         while ((!IsEmptyStr(buf)) && (isspace(buf[len - 1]))) 
732                                 buf[--len] = 0;
733                         if ((bq == 0) &&
734                         ((!strncmp(buf, ">", 1)) || (!strncmp(buf, " >", 2)) )) {
735                                 wprintf("<blockquote>");
736                                 bq = 1;
737                         } else if ((bq == 1) &&
738                                 (strncmp(buf, ">", 1)) && (strncmp(buf, " >", 2)) ) {
739                                 wprintf("</blockquote>");
740                                 bq = 0;
741                         }
742                         wprintf("<tt>");
743                         url(buf, sizeof(buf));
744                         msgescputs1(buf);
745                         wprintf("</tt><br />");
746                 }
747                 wprintf("</i><br />");
748         }
749
750         /** HTML just gets escaped and stuffed back into the editor */
751         else if (!strcasecmp(mime_content_type, "text/html")) {
752                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
753                         strcat(buf, "\n");
754                         msgescputs(buf);
755                 }
756         }//// TODO: charset? utf8?
757
758         /** Unknown weirdness ... don't know how to handle this content type */
759         else {
760                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) { }
761         }
762
763 ENDBODY:
764         /** end of body handler */
765
766         /*
767          * If there were attachments, we have to download them and insert them
768          * into the attachment chain for the forwarded message we are composing.
769          */
770         if ( (forward_attachments) && (num_attachments) ) {
771                 for (i=0; i<num_attachments; ++i) {
772                         extract_token(buf, attachments, i, '\n', sizeof buf);
773                         extract_token(mime_filename, buf, 1, '|', sizeof mime_filename);
774                         extract_token(mime_partnum, buf, 2, '|', sizeof mime_partnum);
775                         extract_token(mime_disposition, buf, 3, '|', sizeof mime_disposition);
776                         extract_token(mime_content_type, buf, 4, '|', sizeof mime_content_type);
777                         mime_length = extract_int(buf, 5);
778
779                         /*
780                          * tracing  ... uncomment if necessary
781                          *
782                          */
783                         lprintf(9, "fwd filename: %s\n", mime_filename);
784                         lprintf(9, "fwd partnum : %s\n", mime_partnum);
785                         lprintf(9, "fwd conttype: %s\n", mime_content_type);
786                         lprintf(9, "fwd dispose : %s\n", mime_disposition);
787                         lprintf(9, "fwd length  : %d\n", mime_length);
788
789                         if ( (!strcasecmp(mime_disposition, "inline"))
790                            || (!strcasecmp(mime_disposition, "attachment")) ) {
791                 
792                                 int n;
793                                 char N[64];
794                                 /* Create an attachment struct from this mime part... */
795                                 att = malloc(sizeof(wc_attachment));
796                                 memset(att, 0, sizeof(wc_attachment));
797                                 att->length = mime_length;
798                                 att->content_type = NewStrBufPlain(mime_content_type, -1);
799                                 att->filename = NewStrBufPlain(mime_filename, -1);
800                                 att->data = load_mimepart(msgnum, mime_partnum);
801                 
802                                 if (WCC->attachments == NULL)
803                                         WCC->attachments = NewHash(1, NULL);
804                                 /* And add it to the list. */
805                                 n = snprintf(N, sizeof N, "%d", GetCount(WCC->attachments) + 1);
806                                 Put(WCC->attachments, N, n, att, free_attachment);
807                         }
808
809                 }
810         }
811
812 #ifdef HAVE_ICONV
813         if (ic != (iconv_t)(-1) ) {
814                 iconv_close(ic);
815         }
816 #endif
817
818         if (attachments != NULL) {
819                 free(attachments);
820         }
821 }
822
823
824
825
826 message_summary *ReadOneMessageSummary(StrBuf *RawMessage, const char *DefaultSubject, long MsgNum) 
827 {
828         void                 *vEval;
829         MsgPartEvaluatorFunc  Eval;
830         message_summary      *Msg;
831         StrBuf *Buf;
832         const char *buf;
833         const char *ebuf;
834         int nBuf;
835         long len;
836         
837         Buf = NewStrBuf();
838
839         serv_printf("MSG0 %ld|1", MsgNum);      /* ask for headers only */
840         
841         StrBuf_ServGetln(Buf);
842         if (GetServerStatus(Buf, NULL) == 1) {
843                 FreeStrBuf(&Buf);
844                 return NULL;
845         }
846
847         Msg = (message_summary*)malloc(sizeof(message_summary));
848         memset(Msg, 0, sizeof(message_summary));
849         while (len = StrBuf_ServGetln(Buf),
850                ((len != 3)  ||
851                 strcmp(ChrPtr(Buf), "000")== 0)){
852                 buf = ChrPtr(Buf);
853                 ebuf = strchr(ChrPtr(Buf), '=');
854                 nBuf = ebuf - buf;
855                 if (GetHash(MsgEvaluators, buf, nBuf, &vEval) && vEval != NULL) {
856                         Eval = (MsgPartEvaluatorFunc) vEval;
857                         StrBufCutLeft(Buf, nBuf + 1);
858                         Eval(Msg, Buf);
859                 }
860                 else lprintf(1, "Don't know how to handle Message Headerline [%s]", ChrPtr(Buf));
861         }
862         return Msg;
863 }
864
865
866 /**
867  * \brief display the adressbook overview
868  * \param msgnum the citadel message number
869  * \param alpha what????
870  */
871 void display_addressbook(long msgnum, char alpha) {
872         //char buf[SIZ];
873         /* char mime_partnum[SIZ]; */
874 /*      char mime_filename[SIZ]; */
875 /*      char mime_content_type[SIZ]; */
876         ///char mime_disposition[SIZ];
877         //int mime_length;
878         char vcard_partnum[SIZ];
879         char *vcard_source = NULL;
880         message_summary summ;////TODO: this will leak
881
882         memset(&summ, 0, sizeof(summ));
883         ///safestrncpy(summ.subj, _("(no subject)"), sizeof summ.subj);
884 ///Load Message headers
885 //      Msg = 
886         if (!IsEmptyStr(vcard_partnum)) {
887                 vcard_source = load_mimepart(msgnum, vcard_partnum);
888                 if (vcard_source != NULL) {
889
890                         /** Display the summary line */
891                         display_vcard(WC->WBuf, vcard_source, alpha, 0, NULL,msgnum);
892
893                         /** If it's my vCard I can edit it */
894                         if (    (!strcasecmp(WC->wc_roomname, USERCONFIGROOM))
895                                 || (!strcasecmp(&WC->wc_roomname[11], USERCONFIGROOM))
896                                 || (WC->wc_view == VIEW_ADDRESSBOOK)
897                         ) {
898                                 wprintf("<a href=\"edit_vcard?"
899                                         "msgnum=%ld&partnum=%s\">",
900                                         msgnum, vcard_partnum);
901                                 wprintf("[%s]</a>", _("edit"));
902                         }
903
904                         free(vcard_source);
905                 }
906         }
907
908 }
909
910
911
912 /**
913  * \brief  If it's an old "Firstname Lastname" style record, try to convert it.
914  * \param namebuf name to analyze, reverse if nescessary
915  */
916 void lastfirst_firstlast(char *namebuf) {
917         char firstname[SIZ];
918         char lastname[SIZ];
919         int i;
920
921         if (namebuf == NULL) return;
922         if (strchr(namebuf, ';') != NULL) return;
923
924         i = num_tokens(namebuf, ' ');
925         if (i < 2) return;
926
927         extract_token(lastname, namebuf, i-1, ' ', sizeof lastname);
928         remove_token(namebuf, i-1, ' ');
929         strcpy(firstname, namebuf);
930         sprintf(namebuf, "%s; %s", lastname, firstname);
931 }
932
933 /**
934  * \brief fetch what??? name
935  * \param msgnum the citadel message number
936  * \param namebuf where to put the name in???
937  */
938 void fetch_ab_name(message_summary *Msg, char *namebuf) {
939         char buf[SIZ];
940         char mime_partnum[SIZ];
941         char mime_filename[SIZ];
942         char mime_content_type[SIZ];
943         char mime_disposition[SIZ];
944         int mime_length;
945         char vcard_partnum[SIZ];
946         char *vcard_source = NULL;
947         int i, len;
948         message_summary summ;/// TODO this will lak
949
950         if (namebuf == NULL) return;
951         strcpy(namebuf, "");
952
953         memset(&summ, 0, sizeof(summ));
954         //////safestrncpy(summ.subj, "(no subject)", sizeof summ.subj);
955
956         sprintf(buf, "MSG0 %ld|0", Msg->msgnum);        /** unfortunately we need the mime info now */
957         serv_puts(buf);
958         serv_getln(buf, sizeof buf);
959         if (buf[0] != '1') return;
960
961         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
962                 if (!strncasecmp(buf, "part=", 5)) {
963                         extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
964                         extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
965                         extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
966                         extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
967                         mime_length = extract_int(&buf[5], 5);
968
969                         if (  (!strcasecmp(mime_content_type, "text/x-vcard"))
970                            || (!strcasecmp(mime_content_type, "text/vcard")) ) {
971                                 strcpy(vcard_partnum, mime_partnum);
972                         }
973
974                 }
975         }
976
977         if (!IsEmptyStr(vcard_partnum)) {
978                 vcard_source = load_mimepart(Msg->msgnum, vcard_partnum);
979                 if (vcard_source != NULL) {
980
981                         /* Grab the name off the card */
982                         display_vcard(WC->WBuf, vcard_source, 0, 0, namebuf, Msg->msgnum);
983
984                         free(vcard_source);
985                 }
986         }
987
988         lastfirst_firstlast(namebuf);
989         striplt(namebuf);
990         len = strlen(namebuf);
991         for (i=0; i<len; ++i) {
992                 if (namebuf[i] != ';') return;
993         }
994         strcpy(namebuf, _("(no name)"));
995 }
996
997
998
999
1000
1001 /*
1002  * load message pointers from the server for a "read messages" operation
1003  *
1004  * servcmd:             the citadel command to send to the citserver
1005  * with_headers:        also include some of the headers with the message numbers (more expensive)
1006  */
1007 int load_msg_ptrs(char *servcmd, int with_headers)
1008 {
1009         StrBuf* FoundCharset = NULL;
1010         struct wcsession *WCC = WC;
1011         message_summary *Msg;
1012         StrBuf *Buf, *Buf2;
1013         ///char buf[1024];
1014         ///time_t datestamp;
1015         //char fullname[128];
1016         //char nodename[128];
1017         //char inetaddr[128];
1018         //char subject[1024];
1019         ///char *ptr;
1020         int nummsgs;
1021         ////int sbjlen;
1022         int maxload = 0;
1023         long len;
1024
1025         ////int num_summ_alloc = 0;
1026
1027         if (WCC->summ != NULL) {
1028                 if (WCC->summ != NULL)
1029                         DeleteHash(&WCC->summ);
1030         }
1031         WCC->summ = NewHash(1, Flathash);
1032         nummsgs = 0;
1033         maxload = 1000;/// TODO
1034         
1035         Buf = NewStrBuf();
1036         serv_puts(servcmd);
1037         StrBuf_ServGetln(Buf);
1038         if (GetServerStatus(Buf, NULL) != 1) {
1039                 FreeStrBuf(&Buf);
1040                 return (nummsgs);
1041         }
1042 // TODO                         if (with_headers) { //// TODO: Have Attachments?
1043         Buf2 = NewStrBuf();
1044         while (len = StrBuf_ServGetln(Buf),
1045                ((len != 3)  ||
1046                 strcmp(ChrPtr(Buf), "000")!= 0))
1047         {
1048                 if (nummsgs < maxload) {
1049                         Msg = (message_summary*)malloc(sizeof(message_summary));
1050                         memset(Msg, 0, sizeof(message_summary));
1051
1052                         Msg->msgnum = StrBufExtract_long(Buf, 0, '|');
1053                         Msg->date = StrBufExtract_long(Buf, 1, '|');
1054
1055                         Msg->from = NewStrBufPlain(NULL, StrLength(Buf));
1056                         StrBufExtract_token(Buf2, Buf, 2, '|');
1057                         if (StrLength(Buf2) != 0) {
1058                                 /** Handle senders with RFC2047 encoding */
1059                                 StrBuf_RFC822_to_Utf8(Msg->from, Buf2, WCC->DefaultCharset, FoundCharset);
1060                         }
1061                         
1062                         /** Nodename */
1063                         StrBufExtract_token(Buf2, Buf, 3, '|');
1064                         if ((StrLength(Buf2) !=0 ) &&
1065                             ( ((WCC->room_flags & QR_NETWORK)
1066                                || ((strcasecmp(ChrPtr(Buf2), serv_info.serv_nodename)
1067                                     && (strcasecmp(ChrPtr(Buf2), serv_info.serv_fqdn)))))))
1068                         {
1069                                 StrBufAppendBufPlain(Msg->from, HKEY(" @ "), 0);
1070                                 StrBufAppendBuf(Msg->from, Buf2, 0);
1071                         }
1072
1073                         /** Not used:
1074                         StrBufExtract_token(Msg->inetaddr, Buf, 4, '|');
1075                         */
1076
1077                         Msg->subj = NewStrBufPlain(NULL, StrLength(Buf));
1078                         StrBufExtract_token(Buf2,  Buf, 5, '|');
1079                         if (StrLength(Buf2) == 0)
1080                                 StrBufAppendBufPlain(Msg->subj, _("(no subj)"), 0, -1);
1081                         else {
1082                                 StrBuf_RFC822_to_Utf8(Msg->subj, Buf2, WCC->DefaultCharset, FoundCharset);
1083                                 if ((StrLength(Msg->subj) > 75) && 
1084                                     (StrBuf_Utf8StrLen(Msg->subj) > 75)) {
1085                                         StrBuf_Utf8StrCut(Msg->subj, 72);
1086                                         StrBufAppendBufPlain(Msg->subj, HKEY("..."), 0);
1087                                 }
1088                         }
1089
1090
1091                         if ((StrLength(Msg->from) > 25) && 
1092                             (StrBuf_Utf8StrLen(Msg->from) > 25)) {
1093                                 StrBuf_Utf8StrCut(Msg->from, 23);
1094                                 StrBufAppendBufPlain(Msg->from, HKEY("..."), 0);
1095                         }
1096                         Put(WCC->summ, (const char*)&Msg->msgnum, sizeof(Msg->msgnum), Msg, DestroyMessageSummary);
1097                 }
1098                 nummsgs++;
1099         }
1100         FreeStrBuf(&Buf2);
1101         FreeStrBuf(&Buf);
1102         return (nummsgs);
1103 }
1104
1105
1106
1107
1108
1109
1110 /*
1111  * command loop for reading messages
1112  *
1113  * Set oper to "readnew" or "readold" or "readfwd" or "headers"
1114  */
1115 void readloop(char *oper)
1116 {
1117         void *vMsg;
1118         message_summary *Msg;
1119         char cmd[256] = "";
1120         char buf[SIZ];
1121         char old_msgs[SIZ];
1122         int a = 0;
1123         int b = 0;
1124         int nummsgs;
1125         long startmsg;
1126         int maxmsgs;
1127         long *displayed_msgs = NULL;
1128         int num_displayed = 0;
1129         int is_summary = 0;
1130         int is_addressbook = 0;
1131         int is_singlecard = 0;
1132         int is_calendar = 0;
1133         struct calview calv;
1134         int is_tasks = 0;
1135         int is_notes = 0;
1136         int is_bbview = 0;
1137         int lo, hi;
1138         int lowest_displayed = (-1);
1139         int highest_displayed = 0;
1140         addrbookent *addrbook = NULL;
1141         int num_ab = 0;
1142         const StrBuf *sortby = NULL;
1143         //SortByEnum 
1144         int SortBy = eRDate;
1145         const StrBuf *sortpref_value;
1146         int bbs_reverse = 0;
1147         struct wcsession *WCC = WC;     /* This is done to make it run faster; WC is a function */
1148         HashPos *at;
1149         const char *HashKey;
1150         long HKLen;
1151
1152         if (WCC->wc_view == VIEW_WIKI) {
1153                 sprintf(buf, "wiki?room=%s&page=home", WCC->wc_roomname);
1154                 http_redirect(buf);
1155                 return;
1156         }
1157
1158         startmsg = lbstr("startmsg");
1159         maxmsgs = ibstr("maxmsgs");
1160         is_summary = (ibstr("is_summary") && !WCC->is_mobile);
1161         if (maxmsgs == 0) maxmsgs = DEFAULT_MAXMSGS;
1162
1163         sortpref_value = get_room_pref("sort");
1164
1165         sortby = sbstr("sortby");
1166         if ( (!IsEmptyStr(ChrPtr(sortby))) && 
1167              (strcasecmp(ChrPtr(sortby), ChrPtr(sortpref_value)) != 0)) {
1168                 set_room_pref("sort", NewStrBufDup(sortby), 1);
1169                 sortpref_value = NULL;
1170                 sortpref_value = sortby;
1171         }
1172
1173         SortBy = StrToESort(sortpref_value);
1174         /* message board sort */
1175         if (SortBy == eReverse) {
1176                 bbs_reverse = 1;
1177         }
1178         else {
1179                 bbs_reverse = 0;
1180         }
1181
1182         output_headers(1, 1, 1, 0, 0, 0);
1183
1184         /*
1185          * When in summary mode, always show ALL messages instead of just
1186          * new or old.  Otherwise, show what the user asked for.
1187          */
1188         if (!strcmp(oper, "readnew")) {
1189                 strcpy(cmd, "MSGS NEW");
1190         }
1191         else if (!strcmp(oper, "readold")) {
1192                 strcpy(cmd, "MSGS OLD");
1193         }
1194         else if (!strcmp(oper, "do_search")) {
1195                 snprintf(cmd, sizeof(cmd), "MSGS SEARCH|%s", bstr("query"));
1196         }
1197         else {
1198                 strcpy(cmd, "MSGS ALL");
1199         }
1200
1201         if ((WCC->wc_view == VIEW_MAILBOX) && (maxmsgs > 1) && !WCC->is_mobile) {
1202                 is_summary = 1;
1203                 if (!strcmp(oper, "do_search")) {
1204                         snprintf(cmd, sizeof(cmd), "MSGS SEARCH|%s", bstr("query"));
1205                 }
1206                 else {
1207                         strcpy(cmd, "MSGS ALL");
1208                 }
1209         }
1210
1211         if ((WCC->wc_view == VIEW_ADDRESSBOOK) && (maxmsgs > 1)) {
1212                 is_addressbook = 1;
1213                 if (!strcmp(oper, "do_search")) {
1214                         snprintf(cmd, sizeof(cmd), "MSGS SEARCH|%s", bstr("query"));
1215                 }
1216                 else {
1217                         strcpy(cmd, "MSGS ALL");
1218                 }
1219                 maxmsgs = 9999999;
1220         }
1221
1222         if (is_summary) {                       /**< fetch header summary */
1223                 snprintf(cmd, sizeof(cmd), "MSGS %s|%s||1",
1224                         (!strcmp(oper, "do_search") ? "SEARCH" : "ALL"),
1225                         (!strcmp(oper, "do_search") ? bstr("query") : "")
1226                 );
1227                 startmsg = 1;
1228                 maxmsgs = 9999999;
1229         } 
1230         if (WCC->is_mobile) {
1231                 maxmsgs = 20;
1232                 snprintf(cmd, sizeof(cmd), "MSGS %s|%s||1",
1233                         (!strcmp(oper, "do_search") ? "SEARCH" : "ALL"),
1234                         (!strcmp(oper, "do_search") ? bstr("query") : "")
1235                 );
1236                 SortBy =  eRDate;
1237         }
1238
1239         /*
1240          * Are we doing a summary view?  If so, we need to know old messages
1241          * and new messages, so we can do that pretty boldface thing for the
1242          * new messages.
1243          */
1244         strcpy(old_msgs, "");
1245         if ((is_summary) || (WCC->wc_default_view == VIEW_CALENDAR) || WCC->is_mobile){
1246                 serv_puts("GTSN");
1247                 serv_getln(buf, sizeof buf);
1248                 if (buf[0] == '2') {
1249                         strcpy(old_msgs, &buf[4]);
1250                 }
1251         }
1252
1253         is_singlecard = ibstr("is_singlecard");
1254
1255         if (WCC->wc_default_view == VIEW_CALENDAR) {            /**< calendar */
1256                 is_calendar = 1;
1257                 strcpy(cmd, "MSGS ALL|||1");
1258                 maxmsgs = 32767;
1259                 parse_calendar_view_request(&calv);
1260         }
1261         if (WCC->wc_default_view == VIEW_TASKS) {               /**< tasks */
1262                 is_tasks = 1;
1263                 strcpy(cmd, "MSGS ALL");
1264                 maxmsgs = 32767;
1265         }
1266         if (WCC->wc_default_view == VIEW_NOTES) {               /**< notes */
1267                 is_notes = 1;
1268                 strcpy(cmd, "MSGS ALL");
1269                 maxmsgs = 32767;
1270         }
1271
1272         if (is_notes) {
1273                 wprintf("<div id=\"new_notes_here\"></div>\n");
1274         }
1275
1276         nummsgs = load_msg_ptrs(cmd, (is_summary || WCC->is_mobile));
1277         if (nummsgs == 0) {
1278
1279                 if ((!is_tasks) && (!is_calendar) && (!is_notes) && (!is_addressbook)) {
1280                         wprintf("<div align=\"center\"><br /><em>");
1281                         if (!strcmp(oper, "readnew")) {
1282                                 wprintf(_("No new messages."));
1283                         } else if (!strcmp(oper, "readold")) {
1284                                 wprintf(_("No old messages."));
1285                         } else {
1286                                 wprintf(_("No messages here."));
1287                         }
1288                         wprintf("</em><br /></div>\n");
1289                 }
1290
1291                 goto DONE;
1292         }
1293
1294         if ((is_summary) || (WCC->wc_default_view == VIEW_CALENDAR) || WCC->is_mobile){
1295                 void *vMsg;
1296                 message_summary *Msg;
1297
1298                 at = GetNewHashPos();
1299                 while (GetNextHashPos(WCC->summ, at, &HKLen, &HashKey, &vMsg)) {
1300                         /** Are you a new message, or an old message? */
1301                         Msg = (message_summary*) vMsg;
1302                         if (is_summary) {
1303                                 if (is_msg_in_mset(old_msgs, Msg->msgnum)) {
1304                                         Msg->is_new = 0;
1305                                 }
1306                                 else {
1307                                         Msg->is_new = 1;
1308                                 }
1309                         }
1310                 }
1311                 DeleteHashPos(&at);
1312         }
1313
1314         if (startmsg == 0L) {
1315                 if (bbs_reverse) {
1316                         startmsg = WCC->msgarr[(nummsgs >= maxmsgs) ? (nummsgs - maxmsgs) : 0];
1317                 }
1318                 else {
1319                         startmsg = WCC->msgarr[0];
1320                 }
1321         }
1322
1323         if (is_summary || WCC->is_mobile) {
1324                 SortByPayload(WCC->summ, SortFuncs[SortBy]);
1325         }
1326
1327         if (is_summary) {
1328
1329                 wprintf("<script language=\"javascript\" type=\"text/javascript\">"
1330                         " document.onkeydown = CtdlMsgListKeyPress;     "
1331                         " if (document.layers) {                        "
1332                         "       document.captureEvents(Event.KEYPRESS); "
1333                         " }                                             "
1334                         "</script>\n"
1335                 );
1336
1337                 /** note that Date and Delete are now in the same column */
1338                 wprintf("<div id=\"message_list_hdr\">"
1339                         "<div class=\"fix_scrollbar_bug\">"
1340                         "<table cellspacing=0 style=\"width:100%%\">"
1341                         "<tr>"
1342                 );
1343                 wprintf("<th width=%d%%>%s <a href=\"readfwd?startmsg=1?maxmsgs=9999999?is_summary=1?sortby=%s\"><img border=\"0\" src=\"%s\" /></a> </th>\n"
1344                         "<th width=%d%%>%s <a href=\"readfwd?startmsg=1?maxmsgs=9999999?is_summary=1?sortby=%s\"><img border=\"0\" src=\"%s\" /></a> </th>\n"
1345                         "<th width=%d%%>%s <a href=\"readfwd?startmsg=1?maxmsgs=9999999?is_summary=1?sortby=%s\"><img border=\"0\" src=\"%s\" /></a> \n"
1346                         "&nbsp;"
1347                         "<input type=\"submit\" name=\"delete_button\" id=\"delbutton\" "
1348                         " onClick=\"CtdlDeleteSelectedMessages(event)\" "
1349                         " value=\"%s\">"
1350                         "</th>"
1351                         "</tr>\n"
1352                         ,
1353                         SUBJ_COL_WIDTH_PCT,
1354                         _("Subject"),
1355                         SortByStrings[SubjectInvertSortString[SortBy]],
1356                         SortIcons[SortSubjectToIcon[SortBy]],
1357                         SENDER_COL_WIDTH_PCT,
1358                         _("Sender"),
1359                         SortByStrings[SenderInvertSortString[SortBy]],
1360                         SortIcons[SortSenderToIcon[SortBy]],
1361                         DATE_PLUS_BUTTONS_WIDTH_PCT,
1362                         _("Date"),
1363                         SortByStrings[DateInvertSortString[SortBy]],
1364                         SortIcons[SortDateToIcon[SortBy]],
1365                         _("Delete")
1366                 );
1367                 wprintf("</table></div></div>\n");
1368                 wprintf("<div id=\"message_list\">"
1369
1370                         "<div class=\"fix_scrollbar_bug\">\n"
1371                         "<table class=\"mailbox_summary\" id=\"summary_headers\" "
1372                         "cellspacing=0 style=\"width:100%%;-moz-user-select:none;\">"
1373                 );
1374         } else if (WCC->is_mobile) {
1375                 wprintf("<div id=\"message_list\">");
1376         }
1377
1378
1379         /**
1380          * Set the "is_bbview" variable if it appears that we are looking at
1381          * a classic bulletin board view.
1382          */
1383         if ((!is_tasks) && (!is_calendar) && (!is_addressbook)
1384               && (!is_notes) && (!is_singlecard) && (!is_summary)) {
1385                 is_bbview = 1;
1386         }
1387
1388         /**
1389          * If we're not currently looking at ALL requested
1390          * messages, then display the selector bar
1391          */
1392         if (is_bbview) {
1393                 /** begin bbview scroller */
1394                 wprintf("<form name=\"msgomatictop\" class=\"selector_top\" > \n <p>");
1395                 wprintf(_("Reading #"));//// TODO this isn't used, should it? : , lowest_displayed, highest_displayed);
1396
1397                 wprintf("<select name=\"whichones\" size=\"1\" "
1398                         "OnChange=\"location.href=msgomatictop.whichones.options"
1399                         "[selectedIndex].value\">\n");
1400
1401                 if (bbs_reverse) {
1402                         for (b=nummsgs-1; b>=0; b = b - maxmsgs) {
1403                                 hi = b + 1;
1404                                 lo = b - maxmsgs + 2;
1405                                 if (lo < 1) lo = 1;
1406                                 wprintf("<option %s value="
1407                                         "\"%s"
1408                                         "&startmsg=%ld"
1409                                         "&maxmsgs=%d"
1410                                         "&is_summary=%d\">"
1411                                         "%d-%d</option> \n",
1412                                         ((WCC->msgarr[lo-1] == startmsg) ? "selected" : ""),
1413                                         oper,
1414                                         WCC->msgarr[lo-1],
1415                                         maxmsgs,
1416                                         is_summary,
1417                                         hi, lo);
1418                         }
1419                 }
1420                 else {
1421                         for (b=0; b<nummsgs; b = b + maxmsgs) {
1422                                 lo = b + 1;
1423                                 hi = b + maxmsgs + 1;
1424                                 if (hi > nummsgs) hi = nummsgs;
1425                                 wprintf("<option %s value="
1426                                         "\"%s"
1427                                         "&startmsg=%ld"
1428                                         "&maxmsgs=%d"
1429                                         "&is_summary=%d\">"
1430                                         "%d-%d</option> \n",
1431                                         ((WCC->msgarr[b] == startmsg) ? "selected" : ""),
1432                                         oper,
1433                                         WCC->msgarr[lo-1],
1434                                         maxmsgs,
1435                                         is_summary,
1436                                         lo, hi);
1437                         }
1438                 }
1439
1440                 wprintf("<option value=\"%s?startmsg=%ld"
1441                         "&maxmsgs=9999999&is_summary=%d\">",
1442                         oper,
1443                         WCC->msgarr[0], is_summary);
1444                 wprintf(_("All"));
1445                 wprintf("</option>");
1446                 wprintf("</select> ");
1447                 wprintf(_("of %d messages."), nummsgs);
1448
1449                 /** forward/reverse */
1450                 wprintf("<input type=\"radio\" %s name=\"direction\" value=\"\""
1451                         "OnChange=\"location.href='%s?sortby=forward'\"",  
1452                         (bbs_reverse ? "" : "checked"),
1453                         oper
1454                 );
1455                 wprintf(">");
1456                 wprintf(_("oldest to newest"));
1457                 wprintf("&nbsp;&nbsp;&nbsp;&nbsp;");
1458
1459                 wprintf("<input type=\"radio\" %s name=\"direction\" value=\"\""
1460                         "OnChange=\"location.href='%s?sortby=reverse'\"", 
1461                         (bbs_reverse ? "checked" : ""),
1462                         oper
1463                 );
1464                 wprintf(">");
1465                 wprintf(_("newest to oldest"));
1466                 wprintf("\n");
1467         
1468                 wprintf("</p></form>\n");
1469                 /** end bbview scroller */
1470         }
1471                         
1472         at = GetNewHashPos();
1473         while (GetNextHashPos(WCC->summ, at, &HKLen, &HashKey, &vMsg)) {
1474                 Msg = (message_summary*) vMsg;          
1475                 if ((Msg->msgnum >= startmsg) && (num_displayed < maxmsgs)) {
1476                                 
1477                         /** Display the message */
1478                         if (is_summary) {
1479                                 DoTemplate(HKEY("section_mailsummary"), NULL, Msg, CTX_MAILSUM);
1480                         }
1481                         else if (is_addressbook) {
1482                                 fetch_ab_name(Msg, buf);
1483                                 ++num_ab;
1484                                 addrbook = realloc(addrbook,
1485                                                    (sizeof(addrbookent) * num_ab) );
1486                                 safestrncpy(addrbook[num_ab-1].ab_name, buf,
1487                                             sizeof(addrbook[num_ab-1].ab_name));
1488                                 addrbook[num_ab-1].ab_msgnum = Msg->msgnum;
1489                         }
1490                         else if (is_calendar) {
1491                                 load_calendar_item(Msg, Msg->is_new, &calv);
1492                         }
1493                         else if (is_tasks) {
1494                                 display_task(Msg, Msg->is_new);
1495                         }
1496                         else if (is_notes) {
1497                                 display_note(Msg, Msg->is_new);
1498                         } else if (WCC->is_mobile) {
1499                                 DoTemplate(HKEY("section_mailsummary"), NULL, Msg, CTX_MAILSUM);
1500                         }
1501                         else {
1502                                 if (displayed_msgs == NULL) {
1503                                         displayed_msgs = malloc(sizeof(long) *
1504                                                                 (maxmsgs<nummsgs ? maxmsgs : nummsgs));
1505                                 }
1506                                 displayed_msgs[num_displayed] = Msg->msgnum;
1507                         }
1508                         
1509                         if (lowest_displayed < 0) lowest_displayed = a;
1510                         highest_displayed = a;
1511                         
1512                         ++num_displayed;
1513                 }
1514         }
1515         DeleteHashPos(&at);
1516
1517         /** Output loop */
1518         if (displayed_msgs != NULL) {
1519                 if (bbs_reverse) {
1520                         qsort(displayed_msgs, num_displayed, sizeof(long), qlongcmp_r);
1521                 }
1522
1523                 /** if we do a split bbview in the future, begin messages div here */
1524
1525                 for (a=0; a<num_displayed; ++a) {
1526                         read_message(WC->WBuf, HKEY("view_message"), displayed_msgs[a], 0, NULL);
1527                 }
1528
1529                 /** if we do a split bbview in the future, end messages div here */
1530
1531                 free(displayed_msgs);
1532                 displayed_msgs = NULL;
1533         }
1534
1535         if (is_summary) {
1536                 wprintf("</table>"
1537                         "</div>\n");                    /**< end of 'fix_scrollbar_bug' div */
1538                 wprintf("</div>");                      /**< end of 'message_list' div */
1539                 
1540                 /** Here's the grab-it-to-resize-the-message-list widget */
1541                 wprintf("<div id=\"resize_msglist\" "
1542                         "onMouseDown=\"CtdlResizeMsgListMouseDown(event)\">"
1543                         "<div class=\"fix_scrollbar_bug\"> <hr>"
1544                         "</div></div>\n"
1545                 );
1546
1547                 wprintf("<div id=\"preview_pane\">");   /**< The preview pane will initially be empty */
1548         } else if (WCC->is_mobile) {
1549                 wprintf("</div>");
1550         }
1551
1552         /**
1553          * Bump these because although we're thinking in zero base, the user
1554          * is a drooling idiot and is thinking in one base.
1555          */
1556         ++lowest_displayed;
1557         ++highest_displayed;
1558
1559         /**
1560          * If we're not currently looking at ALL requested
1561          * messages, then display the selector bar
1562          */
1563         if (is_bbview) {
1564                 /** begin bbview scroller */
1565                 wprintf("<form name=\"msgomatic\" class=\"selector_bottom\" > \n <p>");
1566                 wprintf(_("Reading #")); /// TODO: this isn't used: , lowest_displayed, highest_displayed);
1567
1568                 wprintf("<select name=\"whichones\" size=\"1\" "
1569                         "OnChange=\"location.href=msgomatic.whichones.options"
1570                         "[selectedIndex].value\">\n");
1571
1572                 if (bbs_reverse) {
1573                         for (b=nummsgs-1; b>=0; b = b - maxmsgs) {
1574                                 hi = b + 1;
1575                                 lo = b - maxmsgs + 2;
1576                                 if (lo < 1) lo = 1;
1577                                 wprintf("<option %s value="
1578                                         "\"%s"
1579                                         "&startmsg=%ld"
1580                                         "&maxmsgs=%d"
1581                                         "&is_summary=%d\">"
1582                                         "%d-%d</option> \n",
1583                                         ((WCC->msgarr[lo-1] == startmsg) ? "selected" : ""),
1584                                         oper,
1585                                         WCC->msgarr[lo-1],
1586                                         maxmsgs,
1587                                         is_summary,
1588                                         hi, lo);
1589                         }
1590                 }
1591                 else {
1592                         for (b=0; b<nummsgs; b = b + maxmsgs) {
1593                                 lo = b + 1;
1594                                 hi = b + maxmsgs + 1;
1595                                 if (hi > nummsgs) hi = nummsgs;
1596                                 wprintf("<option %s value="
1597                                         "\"%s"
1598                                         "&startmsg=%ld"
1599                                         "&maxmsgs=%d"
1600                                         "&is_summary=%d\">"
1601                                         "%d-%d</option> \n",
1602                                         ((WCC->msgarr[b] == startmsg) ? "selected" : ""),
1603                                         oper,
1604                                         WCC->msgarr[lo-1],
1605                                         maxmsgs,
1606                                         is_summary,
1607                                         lo, hi);
1608                         }
1609                 }
1610
1611                 wprintf("<option value=\"%s&startmsg=%ld"
1612                         "&maxmsgs=9999999&is_summary=%d\">",
1613                         oper,
1614                         WCC->msgarr[0], is_summary);
1615                 wprintf(_("All"));
1616                 wprintf("</option>");
1617                 wprintf("</select> ");
1618                 wprintf(_("of %d messages."), nummsgs);
1619
1620                 /** forward/reverse */
1621                 wprintf("<input type=\"radio\" %s name=\"direction\" value=\"\""
1622                         "OnChange=\"location.href='%s&sortby=forward'\"",  
1623                         (bbs_reverse ? "" : "checked"),
1624                         oper
1625                 );
1626                 wprintf(">");
1627                 wprintf(_("oldest to newest"));
1628                 wprintf("&nbsp;&nbsp;&nbsp;&nbsp;");
1629                 wprintf("<input type=\"radio\" %s name=\"direction\" value=\"\""
1630                         "OnChange=\"location.href='%s&sortby=reverse'\"", 
1631                         (bbs_reverse ? "checked" : ""),
1632                         oper
1633                 );
1634                 wprintf(">");
1635                 wprintf(_("newest to oldest"));
1636                 wprintf("\n");
1637
1638                 wprintf("</p></form>\n");
1639                 /** end bbview scroller */
1640         }
1641         
1642 DONE:
1643         if (is_tasks) {
1644                 do_tasks_view();        /** Render the task list */
1645         }
1646
1647         if (is_calendar) {
1648                 render_calendar_view(&calv);
1649         }
1650
1651         if (is_addressbook) {
1652                 do_addrbook_view(addrbook, num_ab);     /** Render the address book */
1653         }
1654
1655         /** Note: wDumpContent() will output one additional </div> tag. */
1656         wprintf("</div>\n");            /** end of 'content' div */
1657         wDumpContent(1);
1658
1659         /** free the summary */
1660         if (WCC->summ != NULL) {
1661                 DeleteHash(&WCC->summ);
1662         }
1663         if (addrbook != NULL) free(addrbook);
1664 }
1665
1666
1667 /*
1668  * Back end for post_message()
1669  * ... this is where the actual message gets transmitted to the server.
1670  */
1671 void post_mime_to_server(void) {
1672         struct wcsession *WCC = WC;
1673         char top_boundary[SIZ];
1674         char alt_boundary[SIZ];
1675         int is_multipart = 0;
1676         static int seq = 0;
1677         wc_attachment *att;
1678         char *encoded;
1679         size_t encoded_length;
1680         size_t encoded_strlen;
1681         char *txtmail = NULL;
1682
1683         sprintf(top_boundary, "Citadel--Multipart--%s--%04x--%04x",
1684                 serv_info.serv_fqdn,
1685                 getpid(),
1686                 ++seq
1687         );
1688         sprintf(alt_boundary, "Citadel--Multipart--%s--%04x--%04x",
1689                 serv_info.serv_fqdn,
1690                 getpid(),
1691                 ++seq
1692         );
1693
1694         /* RFC2045 requires this, and some clients look for it... */
1695         serv_puts("MIME-Version: 1.0");
1696         serv_puts("X-Mailer: " PACKAGE_STRING);
1697
1698         /* If there are attachments, we have to do multipart/mixed */
1699         if (GetCount(WCC->attachments) > 0) {
1700                 is_multipart = 1;
1701         }
1702
1703         if (is_multipart) {
1704                 /* Remember, serv_printf() appends an extra newline */
1705                 serv_printf("Content-type: multipart/mixed; boundary=\"%s\"\n", top_boundary);
1706                 serv_printf("This is a multipart message in MIME format.\n");
1707                 serv_printf("--%s", top_boundary);
1708         }
1709
1710         /* Remember, serv_printf() appends an extra newline */
1711         serv_printf("Content-type: multipart/alternative; "
1712                 "boundary=\"%s\"\n", alt_boundary);
1713         serv_printf("This is a multipart message in MIME format.\n");
1714         serv_printf("--%s", alt_boundary);
1715
1716         serv_puts("Content-type: text/plain; charset=utf-8");
1717         serv_puts("Content-Transfer-Encoding: quoted-printable");
1718         serv_puts("");
1719         txtmail = html_to_ascii(bstr("msgtext"), 0, 80, 0);
1720         text_to_server_qp(txtmail);     /* Transmit message in quoted-printable encoding */
1721         free(txtmail);
1722
1723         serv_printf("--%s", alt_boundary);
1724
1725         serv_puts("Content-type: text/html; charset=utf-8");
1726         serv_puts("Content-Transfer-Encoding: quoted-printable");
1727         serv_puts("");
1728         serv_puts("<html><body>\r\n");
1729         text_to_server_qp(bstr("msgtext"));     /* Transmit message in quoted-printable encoding */
1730         serv_puts("</body></html>\r\n");
1731
1732         serv_printf("--%s--", alt_boundary);
1733         
1734         if (is_multipart) {
1735                 long len;
1736                 const char *Key; 
1737                 void *vAtt;
1738                 HashPos  *it;
1739
1740                 /* Add in the attachments */
1741                 it = GetNewHashPos();
1742                 while (GetNextHashPos(WCC->attachments, it, &len, &Key, &vAtt)) {
1743                         att = (wc_attachment*)vAtt;
1744                         encoded_length = ((att->length * 150) / 100);
1745                         encoded = malloc(encoded_length);
1746                         if (encoded == NULL) break;
1747                         encoded_strlen = CtdlEncodeBase64(encoded, att->data, att->length, 1);
1748
1749                         serv_printf("--%s", top_boundary);
1750                         serv_printf("Content-type: %s", ChrPtr(att->content_type));
1751                         serv_printf("Content-disposition: attachment; filename=\"%s\"", ChrPtr(att->filename));
1752                         serv_puts("Content-transfer-encoding: base64");
1753                         serv_puts("");
1754                         serv_write(encoded, encoded_strlen);
1755                         serv_puts("");
1756                         serv_puts("");
1757                         free(encoded);
1758                 }
1759                 serv_printf("--%s--", top_boundary);
1760                 DeleteHashPos(&it);
1761         }
1762
1763         serv_puts("000");
1764 }
1765
1766
1767 /*
1768  * Post message (or don't post message)
1769  *
1770  * Note regarding the "dont_post" variable:
1771  * A random value (actually, it's just a timestamp) is inserted as a hidden
1772  * field called "postseq" when the display_enter page is generated.  This
1773  * value is checked when posting, using the static variable dont_post.  If a
1774  * user attempts to post twice using the same dont_post value, the message is
1775  * discarded.  This prevents the accidental double-saving of the same message
1776  * if the user happens to click the browser "back" button.
1777  */
1778 void post_message(void)
1779 {
1780         char buf[1024];
1781         StrBuf *encoded_subject = NULL;
1782         static long dont_post = (-1L);
1783         wc_attachment *att;
1784         int is_anonymous = 0;
1785         const StrBuf *display_name = NULL;
1786         struct wcsession *WCC = WC;
1787         
1788         if (havebstr("force_room")) {
1789                 gotoroom(bstr("force_room"));
1790         }
1791
1792         if (havebstr("display_name")) {
1793                 display_name = sbstr("display_name");
1794                 if (!strcmp(ChrPtr(display_name), "__ANONYMOUS__")) {
1795                         display_name = NULL;
1796                         is_anonymous = 1;
1797                 }
1798         }
1799
1800         if (WCC->upload_length > 0) {
1801                 const char *pch;
1802                 int n;
1803                 char N[64];
1804
1805                 lprintf(9, "%s:%d: we are uploading %d bytes\n", __FILE__, __LINE__, WCC->upload_length);
1806                 /** There's an attachment.  Save it to this struct... */
1807                 att = malloc(sizeof(wc_attachment));
1808                 memset(att, 0, sizeof(wc_attachment));
1809                 att->length = WCC->upload_length;
1810                 att->content_type = NewStrBufPlain(WCC->upload_content_type, -1);
1811                 att->filename = NewStrBufPlain(WCC->upload_filename, -1);
1812                 
1813                 
1814                 if (WCC->attachments == NULL)
1815                         WCC->attachments = NewHash(1, NULL);
1816                 /* And add it to the list. */
1817                 n = snprintf(N, sizeof N, "%d", GetCount(WCC->attachments) + 1);
1818                 Put(WCC->attachments, N, n, att, free_attachment);
1819
1820                 /**
1821                  * Mozilla sends a simple filename, which is what we want,
1822                  * but Satan's Browser sends an entire pathname.  Reduce
1823                  * the path to just a filename if we need to.
1824                  */
1825                 pch = strrchr(ChrPtr(att->filename), '/');
1826                 if (pch != NULL) {
1827                         StrBufCutLeft(att->filename, pch - ChrPtr(att->filename));
1828                 }
1829                 pch = strrchr(ChrPtr(att->filename), '\\');
1830                 if (pch != NULL) {
1831                         StrBufCutLeft(att->filename, pch - ChrPtr(att->filename));
1832                 }
1833
1834                 /**
1835                  * Transfer control of this memory from the upload struct
1836                  * to the attachment struct.
1837                  */
1838                 att->data = WCC->upload;
1839                 WCC->upload_length = 0;
1840                 WCC->upload = NULL;
1841                 display_enter();
1842                 return;
1843         }
1844
1845         if (havebstr("cancel_button")) {
1846                 sprintf(WCC->ImportantMessage, 
1847                         _("Cancelled.  Message was not posted."));
1848         } else if (havebstr("attach_button")) {
1849                 display_enter();
1850                 return;
1851         } else if (lbstr("postseq") == dont_post) {
1852                 sprintf(WCC->ImportantMessage, 
1853                         _("Automatically cancelled because you have already "
1854                         "saved this message."));
1855         } else {
1856                 const char CMD[] = "ENT0 1|%s|%d|4|%s|%s||%s|%s|%s|%s|%s";
1857                 const StrBuf *Recp = NULL; 
1858                 const StrBuf *Cc = NULL;
1859                 const StrBuf *Bcc = NULL;
1860                 const StrBuf *Wikipage = NULL;
1861                 const StrBuf *my_email_addr = NULL;
1862                 StrBuf *CmdBuf = NULL;;
1863                 StrBuf *references = NULL;
1864
1865                 if (havebstr("references"))
1866                 {
1867                         const StrBuf *ref = sbstr("references");
1868                         references = NewStrBufPlain(ChrPtr(ref), StrLength(ref));
1869                         lprintf(9, "Converting: %s\n", ChrPtr(references));
1870                         StrBufReplaceChars(references, '|', '!');
1871                         lprintf(9, "Converted: %s\n", ChrPtr(references));
1872                 }
1873                 if (havebstr("subject")) {
1874                         const StrBuf *Subj;
1875                         /*
1876                          * make enough room for the encoded string; 
1877                          * plus the QP header 
1878                          */
1879                         Subj = sbstr("subject");
1880                         
1881                         StrBufRFC2047encode(&encoded_subject, Subj);
1882                 }
1883                 Recp = sbstr("recp");
1884                 Cc = sbstr("cc");
1885                 Bcc = sbstr("bcc");
1886                 Wikipage = sbstr("wikipage");
1887                 my_email_addr = sbstr("my_email_addr");
1888                 
1889                 CmdBuf = NewStrBufPlain(NULL, 
1890                                         sizeof (CMD) + 
1891                                         StrLength(Recp) + 
1892                                         StrLength(encoded_subject) +
1893                                         StrLength(Cc) +
1894                                         StrLength(Bcc) + 
1895                                         StrLength(Wikipage) +
1896                                         StrLength(my_email_addr) + 
1897                                         StrLength(references));
1898
1899                 StrBufPrintf(CmdBuf, 
1900                              CMD,
1901                              ChrPtr(Recp),
1902                              is_anonymous,
1903                              ChrPtr(encoded_subject),
1904                              ChrPtr(display_name),
1905                              ChrPtr(Cc),
1906                              ChrPtr(Bcc),
1907                              ChrPtr(Wikipage),
1908                              ChrPtr(my_email_addr),
1909                              ChrPtr(references));
1910                 FreeStrBuf(&references);
1911
1912                 lprintf(9, "%s\n", CmdBuf);
1913                 serv_puts(ChrPtr(CmdBuf));
1914                 serv_getln(buf, sizeof buf);
1915                 FreeStrBuf(&CmdBuf);
1916                 FreeStrBuf(&encoded_subject);
1917                 if (buf[0] == '4') {
1918                         post_mime_to_server();
1919                         if (  (havebstr("recp"))
1920                            || (havebstr("cc"  ))
1921                            || (havebstr("bcc" ))
1922                         ) {
1923                                 sprintf(WCC->ImportantMessage, _("Message has been sent.\n"));
1924                         }
1925                         else {
1926                                 sprintf(WC->ImportantMessage, _("Message has been posted.\n"));
1927                         }
1928                         dont_post = lbstr("postseq");
1929                 } else {
1930                         lprintf(9, "%s:%d: server post error: %s\n", __FILE__, __LINE__, buf);
1931                         sprintf(WC->ImportantMessage, "%s", &buf[4]);
1932                         display_enter();
1933                         return;
1934                 }
1935         }
1936
1937         DeleteHash(&WCC->attachments);
1938
1939         /**
1940          *  We may have been supplied with instructions regarding the location
1941          *  to which we must return after posting.  If found, go there.
1942          */
1943         if (havebstr("return_to")) {
1944                 http_redirect(bstr("return_to"));
1945         }
1946         /**
1947          *  If we were editing a page in a wiki room, go to that page now.
1948          */
1949         else if (havebstr("wikipage")) {
1950                 snprintf(buf, sizeof buf, "wiki?page=%s", bstr("wikipage"));
1951                 http_redirect(buf);
1952         }
1953         /**
1954          *  Otherwise, just go to the "read messages" loop.
1955          */
1956         else {
1957                 readloop("readnew");
1958         }
1959 }
1960
1961
1962
1963
1964 /**
1965  * \brief display the message entry screen
1966  */
1967 void display_enter(void)
1968 {
1969         char buf[SIZ];
1970         StrBuf *ebuf;
1971         long now;
1972         const StrBuf *display_name = NULL;
1973         /////wc_attachment *att;
1974         int recipient_required = 0;
1975         int subject_required = 0;
1976         int recipient_bad = 0;
1977         int is_anonymous = 0;
1978         long existing_page = (-1L);
1979         struct wcsession *WCC = WC;
1980
1981         now = time(NULL);
1982
1983         if (havebstr("force_room")) {
1984                 gotoroom(bstr("force_room"));
1985         }
1986
1987         display_name = sbstr("display_name");
1988         if (!strcmp(ChrPtr(display_name), "__ANONYMOUS__")) {
1989                 display_name = NULL;
1990                 is_anonymous = 1;
1991         }
1992
1993         /** First test to see whether this is a room that requires recipients to be entered */
1994         serv_puts("ENT0 0");
1995         serv_getln(buf, sizeof buf);
1996
1997         if (!strncmp(buf, "570", 3)) {          /** 570 means that we need a recipient here */
1998                 recipient_required = 1;
1999         }
2000         else if (buf[0] != '2') {               /** Any other error means that we cannot continue */
2001                 sprintf(WCC->ImportantMessage, "%s", &buf[4]);
2002                 readloop("readnew");
2003                 return;
2004         }
2005
2006         /* Is the server strongly recommending that the user enter a message subject? */
2007         if ((buf[3] != '\0') && (buf[4] != '\0')) {
2008                 subject_required = extract_int(&buf[4], 1);
2009         }
2010
2011         /**
2012          * Are we perhaps in an address book view?  If so, then an "enter
2013          * message" command really means "add new entry."
2014          */
2015         if (WCC->wc_default_view == VIEW_ADDRESSBOOK) {
2016                 do_edit_vcard(-1, "", "", WCC->wc_roomname);
2017                 return;
2018         }
2019
2020         /*
2021          * Are we perhaps in a calendar room?  If so, then an "enter
2022          * message" command really means "add new calendar item."
2023          */
2024         if (WCC->wc_default_view == VIEW_CALENDAR) {
2025                 display_edit_event();
2026                 return;
2027         }
2028
2029         /*
2030          * Are we perhaps in a tasks view?  If so, then an "enter
2031          * message" command really means "add new task."
2032          */
2033         if (WCC->wc_default_view == VIEW_TASKS) {
2034                 display_edit_task();
2035                 return;
2036         }
2037
2038         /*
2039          * Otherwise proceed normally.
2040          * Do a custom room banner with no navbar...
2041          */
2042         output_headers(1, 1, 2, 0, 0, 0);
2043
2044 /*
2045         wprintf("<div id=\"banner\">\n");
2046         embed_room_banner(NULL, navbar_none);
2047         wprintf("</div>\n");
2048         wprintf("<div id=\"content\">\n"
2049                 "<div class=\"fix_scrollbar_bug message \">");
2050 */
2051         /* Now check our actual recipients if there are any */
2052         if (recipient_required) {
2053                 const StrBuf *Recp = NULL; 
2054                 const StrBuf *Cc = NULL;
2055                 const StrBuf *Bcc = NULL;
2056                 const StrBuf *Wikipage = NULL;
2057                 StrBuf *CmdBuf = NULL;;
2058                 const char CMD[] = "ENT0 0|%s|%d|0||%s||%s|%s|%s";
2059                 
2060                 Recp = sbstr("recp");
2061                 Cc = sbstr("cc");
2062                 Bcc = sbstr("bcc");
2063                 Wikipage = sbstr("wikipage");
2064                 
2065                 CmdBuf = NewStrBufPlain(NULL, 
2066                                         sizeof (CMD) + 
2067                                         StrLength(Recp) + 
2068                                         StrLength(display_name) +
2069                                         StrLength(Cc) +
2070                                         StrLength(Bcc) + 
2071                                         StrLength(Wikipage));
2072
2073                 StrBufPrintf(CmdBuf, 
2074                              CMD,
2075                              ChrPtr(Recp), 
2076                              is_anonymous,
2077                              ChrPtr(display_name),
2078                              ChrPtr(Cc), 
2079                              ChrPtr(Bcc), 
2080                              ChrPtr(Wikipage));
2081                 serv_puts(ChrPtr(CmdBuf));
2082                 serv_getln(buf, sizeof buf);
2083                 FreeStrBuf(&CmdBuf);
2084
2085                 if (!strncmp(buf, "570", 3)) {  /** 570 means we have an invalid recipient listed */
2086                         if (havebstr("recp") && 
2087                             havebstr("cc"  ) && 
2088                             havebstr("bcc" )) {
2089                                 recipient_bad = 1;
2090                         }
2091                 }
2092                 else if (buf[0] != '2') {       /** Any other error means that we cannot continue */
2093                         wprintf("<em>%s</em><br />\n", &buf[4]);/// -> important message
2094                         goto DONE;
2095                 }
2096         }
2097         DoTemplate(HKEY("edit_message"), NULL, NULL, CTX_NONE);
2098         address_book_popup();
2099         wDumpContent(1);
2100
2101
2102         return;
2103
2104
2105         /** If we got this far, we can display the message entry screen. */
2106
2107         /* begin message entry screen */
2108         wprintf("<form "
2109                 "enctype=\"multipart/form-data\" "
2110                 "method=\"POST\" "
2111                 "accept-charset=\"UTF-8\" "
2112                 "action=\"post\" "
2113                 "name=\"enterform\""
2114                 ">\n");
2115         wprintf("<input type=\"hidden\" name=\"postseq\" value=\"%ld\">\n", now);
2116         if (WCC->wc_view == VIEW_WIKI) {
2117                 wprintf("<input type=\"hidden\" name=\"wikipage\" value=\"%s\">\n", bstr("wikipage"));
2118         }
2119         wprintf("<input type=\"hidden\" name=\"return_to\" value=\"%s\">\n", bstr("return_to"));
2120         wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WCC->nonce);
2121         wprintf("<input type=\"hidden\" name=\"force_room\" value=\"");
2122         escputs(WCC->wc_roomname);
2123         wprintf("\">\n");
2124         wprintf("<input type=\"hidden\" name=\"references\" value=\"");
2125         escputs(bstr("references"));
2126         wprintf("\">\n");
2127
2128         /** submit or cancel buttons */
2129         wprintf("<p class=\"send_edit_msg\">");
2130         wprintf("<input type=\"submit\" name=\"send_button\" value=\"");
2131         if (recipient_required) {
2132                 wprintf(_("Send message"));
2133         } else {
2134                 wprintf(_("Post message"));
2135         }
2136         wprintf("\">&nbsp;"
2137                 "<input type=\"submit\" name=\"cancel_button\" value=\"%s\">\n", _("Cancel"));
2138         wprintf("</p>");
2139
2140         /** header bar */
2141
2142         wprintf("<img src=\"static/newmess3_24x.gif\" class=\"imgedit\">");
2143         wprintf("  ");  /** header bar */
2144         webcit_fmt_date(buf, now, 0);
2145         wprintf("%s", buf);
2146         wprintf("\n");  /** header bar */
2147
2148         wprintf("<table width=\"100%%\" class=\"edit_msg_table\">");
2149         wprintf("<tr>");
2150         wprintf("<th><label for=\"from_id\" > ");
2151         wprintf(_(" <I>from</I> "));
2152         wprintf("</label></th>");
2153
2154         wprintf("<td colspan=\"2\">");
2155
2156         /* Allow the user to select any of his valid screen names */
2157
2158         wprintf("<select name=\"display_name\" size=1 id=\"from_id\">\n");
2159
2160         serv_puts("GVSN");
2161         serv_getln(buf, sizeof buf);
2162         if (buf[0] == '1') {
2163                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
2164                         wprintf("<option %s value=\"",
2165                                 ((!strcasecmp(bstr("display_name"), buf)) ? "selected" : "")
2166                         );
2167                         escputs(buf);
2168                         wprintf("\">");
2169                         escputs(buf);
2170                         wprintf("</option>\n");
2171                 }
2172         }
2173
2174         if (WCC->room_flags & QR_ANONOPT) {
2175                 wprintf("<option %s value=\"__ANONYMOUS__\">%s</option>\n",
2176                         ((!strcasecmp(bstr("__ANONYMOUS__"), WCC->wc_fullname)) ? "selected" : ""),
2177                         _("Anonymous")
2178                 );
2179         }
2180
2181         wprintf("</select>\n");
2182
2183         /* If this is an email (not a post), allow the user to select any of his
2184          * valid email addresses.
2185          */
2186         if (recipient_required) {
2187                 serv_puts("GVEA");
2188                 serv_getln(buf, sizeof buf);
2189                 if (buf[0] == '1') {
2190                         wprintf("<select name=\"my_email_addr\" size=1>\n");
2191                         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
2192                                 wprintf("<option value=\"");
2193                                 escputs(buf);
2194                                 wprintf("\">&lt;");
2195                                 escputs(buf);
2196                                 wprintf("&gt;</option>\n");
2197                         }
2198                         wprintf("</select>\n");
2199                 }
2200         }
2201
2202         wprintf(_(" <I>in</I> "));
2203         escputs(WCC->wc_roomname);
2204
2205         wprintf("</td></tr>");
2206
2207         if (recipient_required) {
2208                 char *ccraw;
2209                 char *copy;
2210                 size_t len;
2211                 wprintf("<tr><th><label for=\"recp_id\"> ");
2212                 wprintf(_("To:"));
2213                 wprintf("</label></th>"
2214                         "<td><input autocomplete=\"off\" type=\"text\" name=\"recp\" id=\"recp_id\" value=\"");
2215                 ccraw = xbstr("recp", &len);
2216                 copy = (char*) malloc(len * 2 + 1);
2217                 memcpy(copy, ccraw, len + 1); 
2218                 utf8ify_rfc822_string(copy);
2219                 escputs(copy);
2220                 free(copy);
2221                 wprintf("\" size=45 maxlength=1000 />");
2222                 wprintf("<div class=\"auto_complete\" id=\"recp_name_choices\"></div>");
2223                 wprintf("</td><td rowspan=\"3\" align=\"left\" valign=\"top\">");
2224
2225                 /** Pop open an address book -- begin **/
2226                 wprintf(
2227                         "<a href=\"javascript:PopOpenAddressBook('recp_id|%s|cc_id|%s|bcc_id|%s');\" "
2228                         "title=\"%s\">"
2229                         "<img align=middle border=0 width=24 height=24 src=\"static/viewcontacts_24x.gif\">"
2230                         "&nbsp;%s</a>",
2231                         _("To:"), _("CC:"), _("BCC:"),
2232                         _("Contacts"), _("Contacts")
2233                 );
2234                 /** Pop open an address book -- end **/
2235
2236                 wprintf("</td></tr>");
2237
2238                 wprintf("<tr><th><label for=\"cc_id\"> ");
2239                 wprintf(_("CC:"));
2240                 wprintf("</label></th>"
2241                         "<td><input autocomplete=\"off\" type=\"text\" name=\"cc\" id=\"cc_id\" value=\"");
2242                 ccraw = xbstr("cc", &len);
2243                 copy = (char*) malloc(len * 2 + 1);
2244                 memcpy(copy, ccraw, len + 1); 
2245                 utf8ify_rfc822_string(copy);
2246                 escputs(copy);
2247                 free(copy);
2248                 wprintf("\" size=45 maxlength=1000 />");
2249                 wprintf("<div class=\"auto_complete\" id=\"cc_name_choices\"></div>");
2250                 wprintf("</td></tr>");
2251
2252                 wprintf("<tr><th><label for=\"bcc_id\"> ");
2253                 wprintf(_("BCC:"));
2254                 wprintf("</label></th>"
2255                         "<td><input autocomplete=\"off\" type=\"text\" name=\"bcc\" id=\"bcc_id\" value=\"");
2256                 ccraw = xbstr("bcc", &len);
2257                 copy = (char*) malloc(len * 2 + 1);
2258                 memcpy(copy, ccraw, len + 1); 
2259                 utf8ify_rfc822_string(copy);
2260                 escputs(copy);
2261                 free(copy);
2262                 wprintf("\" size=45 maxlength=1000 />");
2263                 wprintf("<div class=\"auto_complete\" id=\"bcc_name_choices\"></div>");
2264                 wprintf("</td></tr>");
2265
2266                 /** Initialize the autocomplete ajax helpers (found in wclib.js) */
2267                 wprintf("<script type=\"text/javascript\">      \n"
2268                         " activate_entmsg_autocompleters();     \n"
2269                         "</script>                              \n"
2270                 );
2271
2272         }
2273
2274         wprintf("<tr><th><label for=\"subject_id\" > ");
2275         if (recipient_required || subject_required) {
2276                 wprintf(_("Subject:"));
2277         }
2278         else {
2279                 wprintf(_("Subject (optional):"));
2280         }
2281         wprintf("</label></th>"
2282                 "<td colspan=\"2\">"
2283                 "<input type=\"text\" name=\"subject\" id=\"subject_id\" value=\"");
2284         escputs(bstr("subject"));
2285         wprintf("\" size=45 maxlength=70>\n");
2286         wprintf("</td></tr>");
2287
2288         wprintf("<tr><td colspan=\"3\">\n");
2289
2290         wprintf("<textarea name=\"msgtext\" cols=\"80\" rows=\"15\">");
2291
2292         /** If we're continuing from a previous edit, put our partially-composed message back... */
2293         msgescputs(bstr("msgtext"));
2294
2295         /* If we're forwarding a message, insert it here... */
2296         if (lbstr("fwdquote") > 0L) {
2297                 wprintf("<br><div align=center><i>");
2298                 wprintf(_("--- forwarded message ---"));
2299                 wprintf("</i></div><br>");
2300                 pullquote_message(lbstr("fwdquote"), 1, 1);
2301         }
2302
2303         /** If we're replying quoted, insert the quote here... */
2304         else if (lbstr("replyquote") > 0L) {
2305                 wprintf("<br>"
2306                         "<blockquote>");
2307                 pullquote_message(lbstr("replyquote"), 0, 1);
2308                 wprintf("</blockquote><br>");
2309         }
2310
2311         /** If we're editing a wiki page, insert the existing page here... */
2312         else if (WCC->wc_view == VIEW_WIKI) {
2313                 safestrncpy(buf, bstr("wikipage"), sizeof buf);
2314                 str_wiki_index(buf);
2315                 existing_page = locate_message_by_uid(buf);
2316                 if (existing_page >= 0L) {
2317                         pullquote_message(existing_page, 1, 0);
2318                 }
2319         }
2320
2321         /** Insert our signature if appropriate... */
2322         if ( (WCC->is_mailbox) && !yesbstr("sig_inserted") ) {
2323                 int UseSig;
2324                 get_pref_yesno("use_sig", &UseSig, 0);
2325                 if (UseSig) {
2326                         StrBuf *Sig;
2327                         const char *sig, *esig;
2328
2329                         get_preference("signature", &ebuf);
2330                         Sig = NewStrBuf();
2331                         StrBufEUid_unescapize(Sig, ebuf);
2332                         sig = ChrPtr(Sig);
2333                         esig = sig + StrLength(Sig);
2334                         wprintf("<br>--<br>");
2335                         while (sig <= esig) {
2336                                 if (*sig == '\n') {
2337                                         wprintf("<br>");
2338                                 }
2339                                 else if (*sig == '<') {
2340                                         wprintf("&lt;");
2341                                 }
2342                                 else if (*sig == '>') {
2343                                         wprintf("&gt;");
2344                                 }
2345                                 else if (*sig == '&') {
2346                                         wprintf("&amp;");
2347                                 }
2348                                 else if (*sig == '\"') {
2349                                         wprintf("&quot;");
2350                                 }
2351                                 else if (*sig == '\'') {
2352                                         wprintf("&#39;");
2353                                 }
2354                                 else /* since we're utf 8, is this a good idea? if (isprint(*sig))*/ {
2355                                         wprintf("%c", *sig);
2356                                 } 
2357                                 sig ++;
2358                         }
2359                         FreeStrBuf(&Sig);
2360                 }
2361         }
2362
2363         wprintf("</textarea>\n");
2364
2365         /** Make sure we only insert our signature once */
2366         /** We don't care if it was there or not before, it needs to be there now. */
2367         wprintf("<input type=\"hidden\" name=\"sig_inserted\" value=\"yes\">\n");
2368         
2369         /**
2370          * The following template embeds the TinyMCE richedit control, and automatically
2371          * transforms the textarea into a richedit textarea.
2372          */
2373         do_template("richedit", NULL);
2374
2375         /** Enumerate any attachments which are already in place... */
2376         wprintf("<div class=\"attachment buttons\"><img src=\"static/diskette_24x.gif\" class=\"imgedit\" > ");
2377         wprintf(_("Attachments:"));
2378         wprintf(" ");
2379         wprintf("<select name=\"which_attachment\" size=1>");
2380 /*
2381         for (att = WCC->first_attachment; att != NULL; att = att->next) {
2382                 wprintf("<option value=\"");
2383                 urlescputs(att->filename);
2384                 wprintf("\">");
2385                 escputs(att->filename);
2386                 / * wprintf(" (%s, %d bytes)",att->content_type,att->length); * /
2387                 wprintf("</option>\n");
2388         }
2389 */
2390         wprintf("</select>");
2391
2392         /** Now offer the ability to attach additional files... */
2393         wprintf("&nbsp;&nbsp;&nbsp;");
2394         wprintf(_("Attach file:"));
2395         wprintf(" <input name=\"attachfile\" class=\"attachfile\" "
2396                 "size=16 type=\"file\">\n&nbsp;&nbsp;"
2397                 "<input type=\"submit\" name=\"attach_button\" value=\"%s\">\n", _("Add"));
2398         wprintf("</div>");      /* End of "attachment buttons" div */
2399
2400
2401         wprintf("</td></tr></table>");
2402         
2403         wprintf("</form>\n");
2404         wprintf("</div>\n");    /* end of "fix_scrollbar_bug" div */
2405
2406         /* NOTE: address_book_popup() will close the "content" div.  Don't close it here. */
2407 DONE:   address_book_popup();
2408         wDumpContent(1);
2409 }
2410
2411
2412 /**
2413  * \brief delete a message
2414  */
2415 void delete_msg(void)
2416 {
2417         long msgid;
2418         char buf[SIZ];
2419
2420         msgid = lbstr("msgid");
2421
2422         if (WC->wc_is_trash) {  /** Delete from Trash is a real delete */
2423                 serv_printf("DELE %ld", msgid); 
2424         }
2425         else {                  /** Otherwise move it to Trash */
2426                 serv_printf("MOVE %ld|_TRASH_|0", msgid);
2427         }
2428
2429         serv_getln(buf, sizeof buf);
2430         sprintf(WC->ImportantMessage, "%s", &buf[4]);
2431
2432         readloop("readnew");
2433 }
2434
2435
2436 /**
2437  * \brief move a message to another folder
2438  */
2439 void move_msg(void)
2440 {
2441         long msgid;
2442         char buf[SIZ];
2443
2444         msgid = lbstr("msgid");
2445
2446         if (havebstr("move_button")) {
2447                 sprintf(buf, "MOVE %ld|%s", msgid, bstr("target_room"));
2448                 serv_puts(buf);
2449                 serv_getln(buf, sizeof buf);
2450                 sprintf(WC->ImportantMessage, "%s", &buf[4]);
2451         } else {
2452                 sprintf(WC->ImportantMessage, (_("The message was not moved.")));
2453         }
2454
2455         readloop("readnew");
2456 }
2457
2458
2459
2460
2461
2462 /*
2463  * Confirm move of a message
2464  */
2465 void confirm_move_msg(void)
2466 {
2467         long msgid;
2468         char buf[SIZ];
2469         char targ[SIZ];
2470
2471         msgid = lbstr("msgid");
2472
2473
2474         output_headers(1, 1, 2, 0, 0, 0);
2475         wprintf("<div id=\"banner\">\n");
2476         wprintf("<h1>");
2477         wprintf(_("Confirm move of message"));
2478         wprintf("</h1>");
2479         wprintf("</div>\n");
2480
2481         wprintf("<div id=\"content\" class=\"service\">\n");
2482
2483         wprintf("<CENTER>");
2484
2485         wprintf(_("Move this message to:"));
2486         wprintf("<br />\n");
2487
2488         wprintf("<form METHOD=\"POST\" action=\"move_msg\">\n");
2489         wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WC->nonce);
2490         wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgid\" VALUE=\"%s\">\n", bstr("msgid"));
2491
2492         wprintf("<SELECT NAME=\"target_room\" SIZE=5>\n");
2493         serv_puts("LKRA");
2494         serv_getln(buf, sizeof buf);
2495         if (buf[0] == '1') {
2496                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
2497                         extract_token(targ, buf, 0, '|', sizeof targ);
2498                         wprintf("<OPTION>");
2499                         escputs(targ);
2500                         wprintf("\n");
2501                 }
2502         }
2503         wprintf("</SELECT>\n");
2504         wprintf("<br />\n");
2505
2506         wprintf("<INPUT TYPE=\"submit\" NAME=\"move_button\" VALUE=\"%s\">", _("Move"));
2507         wprintf("&nbsp;");
2508         wprintf("<INPUT TYPE=\"submit\" NAME=\"cancel_button\" VALUE=\"%s\">", _("Cancel"));
2509         wprintf("</form></CENTER>\n");
2510
2511         wprintf("</CENTER>\n");
2512         wDumpContent(1);
2513 }
2514
2515 void readnew(void) { readloop("readnew");}
2516 void readold(void) { readloop("readold");}
2517 void readfwd(void) { readloop("readfwd");}
2518 void headers(void) { readloop("headers");}
2519 void do_search(void) { readloop("do_search");}
2520
2521
2522
2523
2524
2525
2526 void 
2527 InitModule_MSG
2528 (void)
2529 {
2530         WebcitAddUrlHandler(HKEY("readnew"), readnew, 0);
2531         WebcitAddUrlHandler(HKEY("readold"), readold, 0);
2532         WebcitAddUrlHandler(HKEY("readfwd"), readfwd, 0);
2533         WebcitAddUrlHandler(HKEY("headers"), headers, 0);
2534         WebcitAddUrlHandler(HKEY("do_search"), do_search, 0);
2535         WebcitAddUrlHandler(HKEY("display_enter"), display_enter, 0);
2536         WebcitAddUrlHandler(HKEY("post"), post_message, 0);
2537         WebcitAddUrlHandler(HKEY("move_msg"), move_msg, 0);
2538         WebcitAddUrlHandler(HKEY("delete_msg"), delete_msg, 0);
2539         WebcitAddUrlHandler(HKEY("confirm_move_msg"), confirm_move_msg, 0);
2540         WebcitAddUrlHandler(HKEY("msg"), embed_message, NEED_URL|AJAX);
2541         WebcitAddUrlHandler(HKEY("printmsg"), print_message, NEED_URL);
2542         WebcitAddUrlHandler(HKEY("mobilemsg"), mobile_message_view, NEED_URL);
2543         WebcitAddUrlHandler(HKEY("msgheaders"), display_headers, NEED_URL);
2544
2545         return ;
2546 }