3f8108f0a97c54511b4f97df414caec04346b535
[citadel.git] / webcit / messages.c
1 /*
2  * Functions which deal with the fetching and displaying of messages.
3  *
4  * Copyright (c) 1996-2012 by the citadel.org team
5  *
6  * This program is open source software.  You can redistribute it and/or
7  * modify it under the terms of the GNU General Public License, version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include "webcit.h"
16 #include "webserver.h"
17 #include "dav.h"
18 #include "calendar.h"
19
20 HashList *MsgHeaderHandler = NULL;
21 HashList *MsgEvaluators = NULL;
22 HashList *MimeRenderHandler = NULL;
23 HashList *ReadLoopHandler = NULL;
24 int dbg_analyze_msg = 0;
25
26 #define SUBJ_COL_WIDTH_PCT              50      /* Mailbox view column width */
27 #define SENDER_COL_WIDTH_PCT            30      /* Mailbox view column width */
28 #define DATE_PLUS_BUTTONS_WIDTH_PCT     20      /* Mailbox view column width */
29
30 void jsonMessageListHdr(void);
31
32 void display_enter(void);
33
34 typedef void (*MsgPartEvaluatorFunc)(message_summary *Sum, StrBuf *Buf);
35
36 typedef struct _MsgPartEvaluatorStruct {
37         MsgPartEvaluatorFunc f;
38 } MsgPartEvaluatorStruct;
39
40 void fixview()
41 {
42         /* workaround for json listview; its not useable directly */
43         if (WC->CurRoom.view == VIEW_JSON_LIST) {
44                 StrBuf *View = NewStrBuf();
45                 StrBufPrintf(View, "%d", VIEW_MAILBOX);
46                 putbstr("view", View);;
47         }
48 }
49 int load_message(message_summary *Msg, 
50                  StrBuf *FoundCharset,
51                  StrBuf **Error)
52 {
53         StrBuf *Buf;
54         StrBuf *HdrToken;
55         headereval *Hdr;
56         void *vHdr;
57         char buf[SIZ];
58         int Done = 0;
59         int state=0;
60         
61         Buf = NewStrBuf();
62         if (Msg->PartNum != NULL) {
63                 serv_printf("MSG4 %ld|%s", Msg->msgnum, ChrPtr(Msg->PartNum));
64         }
65         else {
66                 serv_printf("MSG4 %ld", Msg->msgnum);
67         }
68
69         StrBuf_ServGetln(Buf);
70         if (GetServerStatus(Buf, NULL) != 1) {
71                 *Error = NewStrBuf();
72                 StrBufAppendPrintf(*Error, "<strong>");
73                 StrBufAppendPrintf(*Error, _("ERROR:"));
74                 StrBufAppendPrintf(*Error, "</strong> %s<br>\n", &buf[4]);
75                 FreeStrBuf(&Buf);
76                 return 0;
77         }
78
79         /* begin everythingamundo table */
80         HdrToken = NewStrBuf();
81         while (!Done && StrBuf_ServGetln(Buf)>=0) {
82                 if ( (StrLength(Buf)==3) && 
83                     !strcmp(ChrPtr(Buf), "000")) 
84                 {
85                         Done = 1;
86                         if (state < 2) {
87                                 if (Msg->MsgBody->Data == NULL)
88                                         Msg->MsgBody->Data = NewStrBuf();
89                                 Msg->MsgBody->ContentType = NewStrBufPlain(HKEY("text/html"));
90                                 StrBufAppendPrintf(Msg->MsgBody->Data, "<div><i>");
91                                 StrBufAppendPrintf(Msg->MsgBody->Data, _("Empty message"));
92                                 StrBufAppendPrintf(Msg->MsgBody->Data, "</i><br><br>\n");
93                                 StrBufAppendPrintf(Msg->MsgBody->Data, "</div>\n");
94                         }
95                         break;
96                 }
97                 switch (state) {
98                 case 0:/* Citadel Message Headers */
99                         if (StrLength(Buf) == 0) {
100                                 state ++;
101                                 break;
102                         }
103                         StrBufExtract_token(HdrToken, Buf, 0, '=');
104                         StrBufCutLeft(Buf, StrLength(HdrToken) + 1);
105                         
106                         /* look up one of the examine_* functions to parse the content */
107                         if (GetHash(MsgHeaderHandler, SKEY(HdrToken), &vHdr) &&
108                             (vHdr != NULL)) {
109                                 Hdr = (headereval*)vHdr;
110                                 Hdr->evaluator(Msg, Buf, FoundCharset);
111                                 if (Hdr->Type == 1) {
112                                         state++;
113                                 }
114                         }/* TODO: 
115                         else LogError(Target, 
116                                       __FUNCTION__,  
117                                       "don't know how to handle message header[%s]\n", 
118                                       ChrPtr(HdrToken));
119                          */
120                         break;
121                 case 1:/* Message Mime Header */
122                         if (StrLength(Buf) == 0) {
123                                 state++;
124                                 if (Msg->MsgBody->ContentType == NULL)
125                                         /* end of header or no header? */
126                                         Msg->MsgBody->ContentType = NewStrBufPlain(HKEY("text/plain"));
127                                  /* usual end of mime header */
128                         }
129                         else
130                         {
131                                 StrBufExtract_token(HdrToken, Buf, 0, ':');
132                                 if (StrLength(HdrToken) > 0) {
133                                         StrBufCutLeft(Buf, StrLength(HdrToken) + 1);
134                                         /* the examine*'s know how to do with mime headers too... */
135                                         if (GetHash(MsgHeaderHandler, SKEY(HdrToken), &vHdr) &&
136                                             (vHdr != NULL)) {
137                                                 Hdr = (headereval*)vHdr;
138                                                 Hdr->evaluator(Msg, Buf, FoundCharset);
139                                         }
140                                         break;
141                                 }
142                         }
143                 case 2: /* Message Body */
144                         
145                         if (Msg->MsgBody->size_known > 0) {
146                                 StrBuf_ServGetBLOBBuffered(Msg->MsgBody->Data, Msg->MsgBody->length);
147                                 state ++;
148                                 /*/ todo: check next line, if not 000, append following lines */
149                         }
150                         else if (1){
151                                 if (StrLength(Msg->MsgBody->Data) > 0)
152                                         StrBufAppendBufPlain(Msg->MsgBody->Data, "\n", 1, 0);
153                                 StrBufAppendBuf(Msg->MsgBody->Data, Buf, 0);
154                         }
155                         break;
156                 case 3:
157                         StrBufAppendBuf(Msg->MsgBody->Data, Buf, 0);
158                         break;
159                 }
160         }
161
162         if (Msg->AllAttach == NULL)
163                 Msg->AllAttach = NewHash(1,NULL);
164         /* now we put the body mimepart we read above into the mimelist */
165         Put(Msg->AllAttach, SKEY(Msg->MsgBody->PartNum), Msg->MsgBody, DestroyMime);
166         
167         FreeStrBuf(&Buf);
168         FreeStrBuf(&HdrToken);
169         return 1;
170 }
171
172
173
174 /*
175  * I wanna SEE that message!
176  *
177  * msgnum               Message number to display
178  * printable_view       Nonzero to display a printable view
179  * section              Optional for encapsulated message/rfc822 submessage
180  */
181 int read_message(StrBuf *Target, const char *tmpl, long tmpllen, long msgnum, const StrBuf *PartNum, const StrBuf **OutMime) 
182 {
183         StrBuf *Buf;
184         StrBuf *FoundCharset;
185         HashPos  *it;
186         void *vMime;
187         message_summary *Msg = NULL;
188         void *vHdr;
189         long len;
190         const char *Key;
191         WCTemplputParams SuperTP;
192         WCTemplputParams SubTP;
193         StrBuf *Error = NULL;
194
195         Buf = NewStrBuf();
196         FoundCharset = NewStrBuf();
197         Msg = (message_summary *)malloc(sizeof(message_summary));
198         memset(Msg, 0, sizeof(message_summary));
199         Msg->msgnum = msgnum;
200         Msg->PartNum = PartNum;
201         Msg->MsgBody =  (wc_mime_attachment*) malloc(sizeof(wc_mime_attachment));
202         memset(Msg->MsgBody, 0, sizeof(wc_mime_attachment));
203         Msg->MsgBody->msgnum = msgnum;
204
205         if (!load_message(Msg, FoundCharset, &Error)) {
206                 StrBufAppendBuf(Target, Error, 0);
207                 FreeStrBuf(&Error);
208         }
209
210         /* Extract just the content-type (omit attributes such as "charset") */
211         StrBufExtract_token(Buf, Msg->MsgBody->ContentType, 0, ';');
212         StrBufTrim(Buf);
213         StrBufLowerCase(Buf);
214
215         StackContext(NULL, &SuperTP, Msg, CTX_MAILSUM, 0, NULL);
216         {
217                 /* Locate a renderer capable of converting this MIME part into HTML */
218                 if (GetHash(MimeRenderHandler, SKEY(Buf), &vHdr) &&
219                     (vHdr != NULL)) {
220                         WCTemplputParams SubTP;
221                         RenderMimeFuncStruct *Render;
222                         
223                         StackContext(&SuperTP, &SubTP, Msg->MsgBody, CTX_MIME_ATACH, 0, NULL);
224                         {
225                                 Render = (RenderMimeFuncStruct*)vHdr;
226                                 Render->f(Target, &SubTP, FoundCharset);
227                         }
228                         UnStackContext(&SubTP);
229                 }
230                 
231                 if (StrLength(Msg->reply_references)> 0) {
232                         /* Trim down excessively long lists of thread references.  We eliminate the
233                          * second one in the list so that the thread root remains intact.
234                          */
235                         int rrtok = num_tokens(ChrPtr(Msg->reply_references), '|');
236                         int rrlen = StrLength(Msg->reply_references);
237                         if ( ((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10) ) {
238                                 StrBufRemove_token(Msg->reply_references, 1, '|');
239                         }
240                 }
241
242                 /* now check if we need to translate some mimeparts, and remove the duplicate */
243                 it = GetNewHashPos(Msg->AllAttach, 0);
244                 while (GetNextHashPos(Msg->AllAttach, it, &len, &Key, &vMime) && 
245                        (vMime != NULL)) {
246                         StackContext(&SuperTP, &SubTP, vMime, CTX_MIME_ATACH, 0, NULL);
247                         {
248                                 evaluate_mime_part(Target, &SubTP);
249                         }
250                         UnStackContext(&SubTP);
251                 }
252                 DeleteHashPos(&it);
253                 *OutMime = DoTemplate(tmpl, tmpllen, Target, &SuperTP);
254         }
255         UnStackContext(&SuperTP);
256
257         DestroyMessageSummary(Msg);
258         FreeStrBuf(&FoundCharset);
259         FreeStrBuf(&Buf);
260         return 1;
261 }
262
263
264 long
265 HttpStatus(long CitadelStatus)
266 {
267         long httpstatus = 502;
268         
269         switch (MAJORCODE(CitadelStatus))
270         {
271         case LISTING_FOLLOWS:
272         case CIT_OK:
273                 httpstatus = 201;
274                 break;
275         case ERROR:
276                 switch (MINORCODE(CitadelStatus))
277                 {
278                 case INTERNAL_ERROR:
279                         httpstatus = 403;
280                         break;
281                         
282                 case TOO_BIG:
283                 case ILLEGAL_VALUE:
284                 case HIGHER_ACCESS_REQUIRED:
285                 case MAX_SESSIONS_EXCEEDED:
286                 case RESOURCE_BUSY:
287                 case RESOURCE_NOT_OPEN:
288                 case NOT_HERE:
289                 case INVALID_FLOOR_OPERATION:
290                 case FILE_NOT_FOUND:
291                 case ROOM_NOT_FOUND:
292                         httpstatus = 409;
293                         break;
294
295                 case MESSAGE_NOT_FOUND:
296                 case ALREADY_EXISTS:
297                         httpstatus = 403;
298                         break;
299
300                 case NO_SUCH_SYSTEM:
301                         httpstatus = 502;
302                         break;
303
304                 default:
305                 case CMD_NOT_SUPPORTED:
306                 case PASSWORD_REQUIRED:
307                 case ALREADY_LOGGED_IN:
308                 case USERNAME_REQUIRED:
309                 case NOT_LOGGED_IN:
310                 case SERVER_SHUTTING_DOWN:
311                 case NO_SUCH_USER:
312                 case ASYNC_GEXP:
313                         httpstatus = 502;
314                         break;
315                 }
316                 break;
317
318         default:
319         case BINARY_FOLLOWS:
320         case SEND_BINARY:
321         case START_CHAT_MODE:
322         case ASYNC_MSG:
323         case MORE_DATA:
324         case SEND_LISTING:
325                 httpstatus = 502; /* aeh... whut? */
326                 break;
327         }
328
329         return httpstatus;
330 }
331
332 /*
333  * Unadorned HTML output of an individual message, suitable
334  * for placing in a hidden iframe, for printing, or whatever
335  */
336 void handle_one_message(void) 
337 {
338         long CitStatus = ERROR + NOT_HERE;
339         int CopyMessage = 0;
340         const StrBuf *Destination;
341         void *vLine;
342         const StrBuf *Mime;
343         long msgnum = 0L;
344         wcsession *WCC = WC;
345         const StrBuf *Tmpl;
346         StrBuf *CmdBuf = NULL;
347         const char *pMsg;
348
349
350         pMsg = strchr(ChrPtr(WCC->Hdr->HR.ReqLine), '/');
351         if (pMsg == NULL) {
352                 HttpStatus(CitStatus);
353                 return;
354         }
355
356         msgnum = atol(pMsg + 1);
357         StrBufCutAt(WCC->Hdr->HR.ReqLine, 0, pMsg);
358         gotoroom(WCC->Hdr->HR.ReqLine);
359         switch (WCC->Hdr->HR.eReqType)
360         {
361         case eGET:
362         case ePOST:
363                 Tmpl = sbstr("template");
364                 if (StrLength(Tmpl) > 0) 
365                         read_message(WCC->WBuf, SKEY(Tmpl), msgnum, NULL, &Mime);
366                 else 
367                         read_message(WCC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
368                 http_transmit_thing(ChrPtr(Mime), 0);
369                 break;
370         case eDELETE:
371                 CmdBuf = NewStrBuf ();
372                 if ((WCC->CurRoom.RAFlags & UA_ISTRASH) != 0) { /* Delete from Trash is a real delete */
373                         serv_printf("DELE %ld", msgnum);        
374                 }
375                 else {                  /* Otherwise move it to Trash */
376                         serv_printf("MOVE %ld|_TRASH_|0", msgnum);
377                 }
378                 StrBuf_ServGetln(CmdBuf);
379                 GetServerStatusMsg(CmdBuf, &CitStatus, 1, 0);
380                 HttpStatus(CitStatus);
381                 break;
382         case eCOPY:
383                 CopyMessage = 1;
384         case eMOVE:
385                 if (GetHash(WCC->Hdr->HTTPHeaders, HKEY("DESTINATION"), &vLine) &&
386                     (vLine!=NULL)) {
387                         Destination = (StrBuf*) vLine;
388                         serv_printf("MOVE %ld|%s|%d", msgnum, ChrPtr(Destination), CopyMessage);
389                         StrBuf_ServGetln(CmdBuf);
390                         GetServerStatusMsg(CmdBuf, NULL, 1, 0);
391                         GetServerStatus(CmdBuf, &CitStatus);
392                         HttpStatus(CitStatus);
393                 }
394                 else
395                         HttpStatus(500);
396                 break;
397         default:
398                 break;
399
400         }
401 }
402
403
404 /*
405  * Unadorned HTML output of an individual message, suitable
406  * for placing in a hidden iframe, for printing, or whatever
407  */
408 void embed_message(void) {
409         const StrBuf *Mime;
410         long msgnum = 0L;
411         wcsession *WCC = WC;
412         const StrBuf *Tmpl;
413         StrBuf *CmdBuf = NULL;
414
415         msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
416         if (msgnum <= 0) return;
417
418         switch (WCC->Hdr->HR.eReqType)
419         {
420         case eGET:
421         case ePOST:
422                 Tmpl = sbstr("template");
423                 if (StrLength(Tmpl) > 0) 
424                         read_message(WCC->WBuf, SKEY(Tmpl), msgnum, NULL, &Mime);
425                 else 
426                         read_message(WCC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
427                 http_transmit_thing(ChrPtr(Mime), 0);
428                 break;
429         case eDELETE:
430                 CmdBuf = NewStrBuf ();
431                 if ((WCC->CurRoom.RAFlags & UA_ISTRASH) != 0) { /* Delete from Trash is a real delete */
432                         serv_printf("DELE %ld", msgnum);        
433                 }
434                 else {                  /* Otherwise move it to Trash */
435                         serv_printf("MOVE %ld|_TRASH_|0", msgnum);
436                 }
437                 StrBuf_ServGetln(CmdBuf);
438                 GetServerStatusMsg(CmdBuf, NULL, 1, 0);
439                 break;
440         default:
441                 break;
442
443         }
444 }
445
446
447 /*
448  * Printable view of a message
449  */
450 void print_message(void) {
451         long msgnum = 0L;
452         const StrBuf *Mime;
453
454         msgnum = StrBufExtract_long(WC->Hdr->HR.ReqLine, 0, '/');
455         output_headers(0, 0, 0, 0, 0, 0);
456
457         hprintf("Content-type: text/html\r\n"
458                 "Server: " PACKAGE_STRING "\r\n"
459                 "Connection: close\r\n");
460
461         begin_burst();
462
463         read_message(WC->WBuf, HKEY("view_message_print"), msgnum, NULL, &Mime);
464
465         wDumpContent(0);
466 }
467
468 /*
469  * Display a message's headers
470  */
471 void display_headers(void) {
472         long msgnum = 0L;
473         char buf[1024];
474
475         msgnum = StrBufExtract_long(WC->Hdr->HR.ReqLine, 0, '/');
476         output_headers(0, 0, 0, 0, 0, 0);
477
478         hprintf("Content-type: text/plain\r\n"
479                 "Server: %s\r\n"
480                 "Connection: close\r\n",
481                 PACKAGE_STRING);
482         begin_burst();
483
484         serv_printf("MSG2 %ld|3", msgnum);
485         serv_getln(buf, sizeof buf);
486         if (buf[0] == '1') {
487                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
488                         wc_printf("%s\n", buf);
489                 }
490         }
491
492         wDumpContent(0);
493 }
494
495
496 message_summary *ReadOneMessageSummary(StrBuf *RawMessage, const char *DefaultSubject, long MsgNum) 
497 {
498         void                 *vEval;
499         MsgPartEvaluatorStruct  *Eval;
500         message_summary      *Msg;
501         StrBuf *Buf;
502         const char *buf;
503         const char *ebuf;
504         int nBuf;
505         long len;
506         
507         Buf = NewStrBuf();
508
509         serv_printf("MSG0 %ld|1", MsgNum);      /* ask for headers only */
510         
511         StrBuf_ServGetln(Buf);
512         if (GetServerStatus(Buf, NULL) == 1) {
513                 FreeStrBuf(&Buf);
514                 return NULL;
515         }
516
517         Msg = (message_summary*)malloc(sizeof(message_summary));
518         memset(Msg, 0, sizeof(message_summary));
519         while (len = StrBuf_ServGetln(Buf),
520                (len >= 0) && 
521                ((len != 3)  ||
522                 strcmp(ChrPtr(Buf), "000")))
523         {
524                 buf = ChrPtr(Buf);
525                 ebuf = strchr(ChrPtr(Buf), '=');
526                 nBuf = ebuf - buf;
527                 if (GetHash(MsgEvaluators, buf, nBuf, &vEval) && vEval != NULL) {
528                         Eval = (MsgPartEvaluatorStruct*) vEval;
529                         StrBufCutLeft(Buf, nBuf + 1);
530                         Eval->f(Msg, Buf);
531                 }
532                 else syslog(1, "Don't know how to handle Message Headerline [%s]", ChrPtr(Buf));
533         }
534         return Msg;
535 }
536
537
538
539
540
541 /*
542  * load message pointers from the server for a "read messages" operation
543  *
544  * servcmd:             the citadel command to send to the citserver
545  */
546 int load_msg_ptrs(const char *servcmd,
547                   const char *filter,
548                   SharedMessageStatus *Stat, 
549                   load_msg_ptrs_detailheaders LH)
550 {
551         wcsession *WCC = WC;
552         message_summary *Msg;
553         StrBuf *Buf, *Buf2;
554         long len;
555         int n;
556         int skipit;
557         const char *Ptr = NULL;
558         int StatMajor;
559
560         Stat->lowest_found = LONG_MAX;
561         Stat->highest_found = LONG_MIN;
562
563         if (WCC->summ != NULL) {
564                 DeleteHash(&WCC->summ);
565         }
566         WCC->summ = NewHash(1, Flathash);
567         
568         Buf = NewStrBuf();
569         serv_puts(servcmd);
570         StrBuf_ServGetln(Buf);
571         StatMajor = GetServerStatus(Buf, NULL);
572         switch (StatMajor) {
573         case 1:
574                 break;
575         case 8:
576                 if (filter != NULL) {
577                         serv_puts(filter);
578                         serv_puts("000");
579                         break;
580                 }
581                 /* fall back to empty filter in case of we were fooled... */
582                 serv_puts("");
583                 serv_puts("000");
584                 break;
585         default:
586                 FreeStrBuf(&Buf);
587                 return (Stat->nummsgs);
588         }
589         Buf2 = NewStrBuf();
590         while (len = StrBuf_ServGetln(Buf), 
591                ((len >= 0) &&
592                 ((len != 3) || 
593                  strcmp(ChrPtr(Buf), "000")!= 0)))
594         {
595                 if (Stat->nummsgs < Stat->maxload) {
596                         skipit = 0;
597                         Ptr = NULL;
598                         Msg = (message_summary*)malloc(sizeof(message_summary));
599                         memset(Msg, 0, sizeof(message_summary));
600
601                         Msg->msgnum = StrBufExtractNext_long(Buf, &Ptr, '|');
602                         Msg->date = StrBufExtractNext_long(Buf, &Ptr, '|');
603
604                         if (Stat->nummsgs == 0) {
605                                 if (Msg->msgnum < Stat->lowest_found) {
606                                         Stat->lowest_found = Msg->msgnum;
607                                 }
608                                 if (Msg->msgnum > Stat->highest_found) {
609                                         Stat->highest_found = Msg->msgnum;
610                                 }
611                         }
612
613                         if ((Msg->msgnum == 0) && (StrLength(Buf) < 32)) {
614                                 free(Msg);
615                                 continue;
616                         }
617
618                         /* 
619                          * as citserver probably gives us messages in forward date sorting
620                          * nummsgs should be the same order as the message date.
621                          */
622                         if (Msg->date == 0) {
623                                 Msg->date = Stat->nummsgs;
624                                 if (StrLength(Buf) < 32) 
625                                         skipit = 1;
626                         }
627                         if ((!skipit) && (LH != NULL)) {
628                                 if (!LH(Buf, &Ptr, Msg, Buf2)){
629                                         free(Msg);
630                                         continue;
631                                 }                                       
632                         }
633                         n = Msg->msgnum;
634                         Put(WCC->summ, (const char *)&n, sizeof(n), Msg, DestroyMessageSummary);
635                 }
636                 Stat->nummsgs++;
637         }
638         FreeStrBuf(&Buf2);
639         FreeStrBuf(&Buf);
640         return (Stat->nummsgs);
641 }
642
643
644
645 /**
646  * \brief sets FlagToSet for each of ScanMe that appears in MatchMSet
647  * \param ScanMe List of BasicMsgStruct to be searched it MatchSet
648  * \param MatchMSet MSet we want to flag
649  * \param FlagToSet Flag to set on each BasicMsgStruct->Flags if in MSet
650  */
651 void SetFlagsFromMSet(HashList *ScanMe, MSet *MatchMSet, int FlagToSet, int Reverse)
652 {
653         const char *HashKey;
654         long HKLen;
655         HashPos *at;
656         void *vMsg;
657         message_summary *Msg;
658
659         at = GetNewHashPos(ScanMe, 0);
660         while (GetNextHashPos(ScanMe, at, &HKLen, &HashKey, &vMsg)) {
661                 /* Are you a new message, or an old message? */
662                 Msg = (message_summary*) vMsg;
663                 if (Reverse && IsInMSetList(MatchMSet, Msg->msgnum)) {
664                         Msg->Flags = Msg->Flags | FlagToSet;
665                 }
666                 else if (!Reverse && !IsInMSetList(MatchMSet, Msg->msgnum)) {
667                         Msg->Flags = Msg->Flags | FlagToSet;
668                 }
669         }
670         DeleteHashPos(&at);
671 }
672
673
674 void load_seen_flags(void)
675 {
676         StrBuf *OldMsg;
677         wcsession *WCC = WC;
678         MSet *MatchMSet;
679
680         OldMsg = NewStrBuf();
681         serv_puts("GTSN");
682         StrBuf_ServGetln(OldMsg);
683         if (GetServerStatus(OldMsg, NULL) == 2) {
684                 StrBufCutLeft(OldMsg, 4);
685         }
686         else {
687                 FreeStrBuf(&OldMsg);
688                 return;
689         }
690
691         if (ParseMSet(&MatchMSet, OldMsg))
692         {
693                 SetFlagsFromMSet(WCC->summ, MatchMSet, MSGFLAG_READ, 0);
694         }
695         DeleteMSet(&MatchMSet);
696         FreeStrBuf(&OldMsg);
697 }
698
699 extern readloop_struct rlid[];
700
701 typedef struct _RoomRenderer{
702         int RoomType;
703
704         GetParamsGetServerCall_func GetParamsGetServerCall;
705         
706         PrintViewHeader_func PrintPageHeader;
707         PrintViewHeader_func PrintViewHeader;
708         LoadMsgFromServer_func LoadMsgFromServer;
709         RenderView_or_Tail_func RenderView_or_Tail;
710         View_Cleanup_func ViewCleanup;
711         load_msg_ptrs_detailheaders LHParse;
712 } RoomRenderer;
713
714
715 /*
716  * command loop for reading messages
717  *
718  * Set oper to "readnew" or "readold" or "readfwd" or "headers" or "readgt" or "readlt" or "do_search"
719  */
720 void readloop(long oper, eCustomRoomRenderer ForceRenderer)
721 {
722         RoomRenderer *ViewMsg;
723         void *vViewMsg;
724         void *vMsg;
725         message_summary *Msg;
726         char cmd[256] = "";
727         char filter[256] = "";
728         int i, r;
729         wcsession *WCC = WC;
730         HashPos *at;
731         const char *HashKey;
732         long HKLen;
733         WCTemplputParams SubTP;
734         SharedMessageStatus Stat;
735         void *ViewSpecific;
736
737         if (havebstr("is_summary") && (1 == (ibstr("is_summary")))) {
738                 WCC->CurRoom.view = VIEW_MAILBOX;
739         }
740
741         if (havebstr("view")) {
742                 WCC->CurRoom.view = ibstr("view");
743         }
744
745         memset(&Stat, 0, sizeof(SharedMessageStatus));
746         Stat.maxload = 10000;
747         Stat.lowest_found = (-1);
748         Stat.highest_found = (-1);
749         if (ForceRenderer == eUseDefault)
750                 GetHash(ReadLoopHandler, IKEY(WCC->CurRoom.view), &vViewMsg);
751         else 
752                 GetHash(ReadLoopHandler, IKEY(ForceRenderer), &vViewMsg);
753         if (vViewMsg == NULL) {
754                 WCC->CurRoom.view = VIEW_BBS;
755                 GetHash(ReadLoopHandler, IKEY(WCC->CurRoom.view), &vViewMsg);
756         }
757         if (vViewMsg == NULL) {
758                 return;                 /* TODO: print message */
759         }
760
761         ViewMsg = (RoomRenderer*) vViewMsg;
762         if (ViewMsg->PrintPageHeader == NULL)
763                 output_headers(1, 1, 1, 0, 0, 0);
764         else 
765                 ViewMsg->PrintPageHeader(&Stat, ViewSpecific);
766
767         if (ViewMsg->GetParamsGetServerCall != NULL) {
768                 r = ViewMsg->GetParamsGetServerCall(
769                        &Stat,
770                        &ViewSpecific,
771                        oper,
772                        cmd, sizeof(cmd),
773                        filter, sizeof(filter)
774                 );
775         } else {
776                 r = 0;
777         }
778
779         switch(r)
780         {
781         case 400:
782         case 404:
783
784                 return;
785         case 300: /* the callback hook should do the work for us here, since he knows what to do. */
786                 return;
787         case 200:
788         default:
789                 break;
790         }
791         if (!IsEmptyStr(cmd)) {
792                 const char *p = NULL;
793                 if (!IsEmptyStr(filter))
794                         p = filter;
795                 Stat.nummsgs = load_msg_ptrs(cmd, p, &Stat, ViewMsg->LHParse);
796         }
797
798         if (Stat.sortit) {
799                 CompareFunc SortIt;
800                 StackContext(NULL, &SubTP, NULL, CTX_MAILSUM, 0, NULL);
801                 {
802                         SortIt =  RetrieveSort(&SubTP,
803                                                NULL, 0,
804                                                HKEY("date"),
805                                                Stat.defaultsortorder);
806                 }
807                 UnStackContext(&SubTP);
808                 if (SortIt != NULL)
809                         SortByPayload(WCC->summ, SortIt);
810         }
811         if (Stat.startmsg < 0) {
812                 Stat.startmsg =  0;
813         }
814
815         if (Stat.load_seen) load_seen_flags();
816         
817         /*
818          * Print any inforation above the message list...
819          */
820         if (ViewMsg->PrintViewHeader != NULL)
821                 ViewMsg->PrintViewHeader(&Stat, &ViewSpecific);
822
823         WCC->startmsg =  Stat.startmsg;
824         WCC->maxmsgs = Stat.maxmsgs;
825         WCC->num_displayed = 0;
826
827         /* Put some helpful data in vars for mailsummary_json */
828         {
829                 StrBuf *Foo;
830                 
831                 Foo = NewStrBuf ();
832                 StrBufPrintf(Foo, "%ld", Stat.nummsgs);
833                 PutBstr(HKEY("__READLOOP:TOTALMSGS"), NewStrBufDup(Foo));
834                 StrBufPrintf(Foo, "%ld", Stat.startmsg);
835                 PutBstr(HKEY("__READLOOP:STARTMSG"), Foo);
836         }
837
838         /*
839          * iterate over each message. if we need to load an attachment, do it here. 
840          */
841
842         if ((ViewMsg->LoadMsgFromServer != NULL) && 
843             (!IsEmptyStr(cmd)))
844         {
845                 at = GetNewHashPos(WCC->summ, 0);
846                 Stat.num_displayed = i = 0;
847                 while ( GetNextHashPos(WCC->summ, at, &HKLen, &HashKey, &vMsg)) {
848                         Msg = (message_summary*) vMsg;          
849                         if ((Msg->msgnum >= Stat.startmsg) && (Stat.num_displayed <= Stat.maxmsgs)) {
850                                 ViewMsg->LoadMsgFromServer(&Stat, 
851                                                            &ViewSpecific, 
852                                                            Msg, 
853                                                            (Msg->Flags & MSGFLAG_READ) != 0, 
854                                                            i);
855                         } 
856                         i++;
857                 }
858                 DeleteHashPos(&at);
859         }
860
861         /*
862          * Done iterating the message list. now tasks we want to do after.
863          */
864         if (ViewMsg->RenderView_or_Tail != NULL)
865                 ViewMsg->RenderView_or_Tail(&Stat, &ViewSpecific, oper);
866
867         if (ViewMsg->ViewCleanup != NULL)
868                 ViewMsg->ViewCleanup(&ViewSpecific);
869
870         WCC->startmsg = 0;
871         WCC->maxmsgs = 0;
872         if (WCC->summ != NULL) {
873                 DeleteHash(&WCC->summ);
874         }
875 }
876
877
878 /*
879  * Back end for post_message()
880  * ... this is where the actual message gets transmitted to the server.
881  */
882 void post_mime_to_server(void) {
883         wcsession *WCC = WC;
884         char top_boundary[SIZ];
885         char alt_boundary[SIZ];
886         int is_multipart = 0;
887         static int seq = 0;
888         wc_mime_attachment *att;
889         char *encoded;
890         size_t encoded_length;
891         size_t encoded_strlen;
892         char *txtmail = NULL;
893         int include_text_alt = 0;       /* Set to nonzero to include multipart/alternative text/plain */
894
895         sprintf(top_boundary, "Citadel--Multipart--%s--%04x--%04x",
896                 ChrPtr(WCC->serv_info->serv_fqdn),
897                 getpid(),
898                 ++seq
899         );
900         sprintf(alt_boundary, "Citadel--Multipart--%s--%04x--%04x",
901                 ChrPtr(WCC->serv_info->serv_fqdn),
902                 getpid(),
903                 ++seq
904         );
905
906         /* RFC2045 requires this, and some clients look for it... */
907         serv_puts("MIME-Version: 1.0");
908         serv_puts("X-Mailer: " PACKAGE_STRING);
909
910         /* If there are attachments, we have to do multipart/mixed */
911         if (GetCount(WCC->attachments) > 0) {
912                 is_multipart = 1;
913         }
914
915         /* Only do multipart/alternative for mailboxes.  BBS and Wiki rooms don't need it. */
916         if ((WCC->CurRoom.view == VIEW_MAILBOX) ||
917             (WCC->CurRoom.view == VIEW_JSON_LIST))
918         {
919                 include_text_alt = 1;
920         }
921
922         if (is_multipart) {
923                 /* Remember, serv_printf() appends an extra newline */
924                 serv_printf("Content-type: multipart/mixed; boundary=\"%s\"\n", top_boundary);
925                 serv_printf("This is a multipart message in MIME format.\n");
926                 serv_printf("--%s", top_boundary);
927         }
928
929         /* Remember, serv_printf() appends an extra newline */
930         if (include_text_alt) {
931                 serv_printf("Content-type: multipart/alternative; "
932                         "boundary=\"%s\"\n", alt_boundary);
933                 serv_printf("This is a multipart message in MIME format.\n");
934                 serv_printf("--%s", alt_boundary);
935
936                 serv_puts("Content-type: text/plain; charset=utf-8");
937                 serv_puts("Content-Transfer-Encoding: quoted-printable");
938                 serv_puts("");
939                 txtmail = html_to_ascii(bstr("msgtext"), 0, 80, 0);
940                 text_to_server_qp(txtmail);     /* Transmit message in quoted-printable encoding */
941                 free(txtmail);
942
943                 serv_printf("\n--%s", alt_boundary);
944         }
945
946         serv_puts("Content-type: text/html; charset=utf-8");
947         serv_puts("Content-Transfer-Encoding: quoted-printable");
948         serv_puts("");
949         serv_puts("<html><body>\r\n");
950         text_to_server_qp(bstr("msgtext"));     /* Transmit message in quoted-printable encoding */
951         serv_puts("</body></html>\r\n");
952
953         if (include_text_alt) {
954                 serv_printf("--%s--", alt_boundary);
955         }
956         
957         if (is_multipart) {
958                 long len;
959                 const char *Key; 
960                 void *vAtt;
961                 HashPos  *it;
962
963                 /* Add in the attachments */
964                 it = GetNewHashPos(WCC->attachments, 0);
965                 while (GetNextHashPos(WCC->attachments, it, &len, &Key, &vAtt)) {
966                         att = (wc_mime_attachment *)vAtt;
967                         if (att->length == 0)
968                                 continue;
969                         encoded_length = ((att->length * 150) / 100);
970                         encoded = malloc(encoded_length);
971                         if (encoded == NULL) break;
972                         encoded_strlen = CtdlEncodeBase64(encoded, ChrPtr(att->Data), StrLength(att->Data), 1);
973
974                         serv_printf("--%s", top_boundary);
975                         serv_printf("Content-type: %s", ChrPtr(att->ContentType));
976                         serv_printf("Content-disposition: attachment; filename=\"%s\"", ChrPtr(att->FileName));
977                         serv_puts("Content-transfer-encoding: base64");
978                         serv_puts("");
979                         serv_write(encoded, encoded_strlen);
980                         serv_puts("");
981                         serv_puts("");
982                         free(encoded);
983                 }
984                 serv_printf("--%s--", top_boundary);
985                 DeleteHashPos(&it);
986         }
987
988         serv_puts("000");
989 }
990
991
992 /*
993  * Post message (or don't post message)
994  *
995  * Note regarding the "dont_post" variable:
996  * A random value (actually, it's just a timestamp) is inserted as a hidden
997  * field called "postseq" when the display_enter page is generated.  This
998  * value is checked when posting, using the static variable dont_post.  If a
999  * user attempts to post twice using the same dont_post value, the message is
1000  * discarded.  This prevents the accidental double-saving of the same message
1001  * if the user happens to click the browser "back" button.
1002  */
1003 void post_message(void)
1004 {
1005         StrBuf *UserName;
1006         StrBuf *EmailAddress;
1007         StrBuf *EncBuf;
1008         char buf[1024];
1009         StrBuf *encoded_subject = NULL;
1010         static long dont_post = (-1L);
1011         int is_anonymous = 0;
1012         const StrBuf *display_name = NULL;
1013         wcsession *WCC = WC;
1014         StrBuf *Buf;
1015         
1016         if (havebstr("force_room")) {
1017                 gotoroom(sbstr("force_room"));
1018         }
1019
1020         if (havebstr("display_name")) {
1021                 display_name = sbstr("display_name");
1022                 if (!strcmp(ChrPtr(display_name), "__ANONYMOUS__")) {
1023                         display_name = NULL;
1024                         is_anonymous = 1;
1025                 }
1026         }
1027
1028         if (!strcasecmp(bstr("submit_action"), "cancel")) {
1029                 AppendImportantMessage(_("Cancelled.  Message was not posted."), -1);
1030         } else if (lbstr("postseq") == dont_post) {
1031                 AppendImportantMessage(
1032                         _("Automatically cancelled because you have already "
1033                           "saved this message."), -1);
1034         } else {
1035                 const char CMD[] = "ENT0 1|%s|%d|4|%s|%s||%s|%s|%s|%s|%s";
1036                 StrBuf *Recp = NULL; 
1037                 StrBuf *Cc = NULL;
1038                 StrBuf *Bcc = NULL;
1039                 char *wikipage = NULL;
1040                 const StrBuf *my_email_addr = NULL;
1041                 StrBuf *CmdBuf = NULL;
1042                 StrBuf *references = NULL;
1043                 int saving_to_drafts = 0;
1044                 long HeaderLen = 0;
1045
1046                 saving_to_drafts = !strcasecmp(bstr("submit_action"), "draft");
1047                 Buf = NewStrBuf();
1048
1049                 if (saving_to_drafts) {
1050                         /* temporarily change to the drafts room */
1051                         serv_puts("GOTO _DRAFTS_");
1052                         StrBuf_ServGetln(Buf);
1053                         if (GetServerStatusMsg(Buf, NULL, 1, 2) != 2) {
1054                                 /* You probably don't even have a dumb Drafts folder */
1055                                 syslog(9, "%s:%d: server save to drafts error: %s\n", __FILE__, __LINE__, ChrPtr(Buf) + 4);
1056                                 AppendImportantMessage(_("Saved to Drafts failed: "), -1);
1057                                 display_enter();
1058                                 FreeStrBuf(&Buf);
1059                                 return;
1060                         }
1061                 }
1062
1063                 if (havebstr("references"))
1064                 {
1065                         const StrBuf *ref = sbstr("references");
1066                         references = NewStrBufDup(ref);
1067                         if (*ChrPtr(references) == '|') {       /* remove leading '|' if present */
1068                                 StrBufCutLeft(references, 1);
1069                         }
1070                         StrBufReplaceChars(references, '|', '!');
1071                 }
1072                 if (havebstr("subject")) {
1073                         const StrBuf *Subj;
1074                         /*
1075                          * make enough room for the encoded string; 
1076                          * plus the QP header 
1077                          */
1078                         Subj = sbstr("subject");
1079                         
1080                         StrBufRFC2047encode(&encoded_subject, Subj);
1081                 }
1082                 UserName = NewStrBuf();
1083                 EmailAddress = NewStrBuf();
1084                 EncBuf = NewStrBuf();
1085
1086                 Recp = StrBufSanitizeEmailRecipientVector(sbstr("recp"), UserName, EmailAddress, EncBuf);
1087                 Cc = StrBufSanitizeEmailRecipientVector(sbstr("cc"), UserName, EmailAddress, EncBuf);
1088                 Bcc = StrBufSanitizeEmailRecipientVector(sbstr("bcc"), UserName, EmailAddress, EncBuf);
1089
1090                 FreeStrBuf(&UserName);
1091                 FreeStrBuf(&EmailAddress);
1092                 FreeStrBuf(&EncBuf);
1093
1094                 wikipage = strdup(bstr("page"));
1095                 str_wiki_index(wikipage);
1096                 my_email_addr = sbstr("my_email_addr");
1097                 
1098                 HeaderLen = StrLength(Recp) + 
1099                         StrLength(encoded_subject) +
1100                         StrLength(Cc) +
1101                         StrLength(Bcc) + 
1102                         strlen(wikipage) +
1103                         StrLength(my_email_addr) + 
1104                         StrLength(references);
1105                 CmdBuf = NewStrBufPlain(NULL, sizeof (CMD) + HeaderLen);
1106                 StrBufPrintf(CmdBuf, 
1107                              CMD,
1108                              saving_to_drafts?"":ChrPtr(Recp),
1109                              is_anonymous,
1110                              ChrPtr(encoded_subject),
1111                              ChrPtr(display_name),
1112                              saving_to_drafts?"":ChrPtr(Cc),
1113                              saving_to_drafts?"":ChrPtr(Bcc),
1114                              wikipage,
1115                              ChrPtr(my_email_addr),
1116                              ChrPtr(references));
1117                 FreeStrBuf(&references);
1118                 FreeStrBuf(&encoded_subject);
1119                 free(wikipage);
1120
1121                 if ((HeaderLen + StrLength(sbstr("msgtext")) < 10) && 
1122                     (GetCount(WCC->attachments) == 0)){
1123                         AppendImportantMessage(_("Refusing to post empty message.\n"), -1);
1124                         FreeStrBuf(&CmdBuf);
1125                                 
1126                 }
1127                 else 
1128                 {
1129                         syslog(9, "%s\n", ChrPtr(CmdBuf));
1130                         serv_puts(ChrPtr(CmdBuf));
1131                         FreeStrBuf(&CmdBuf);
1132
1133                         StrBuf_ServGetln(Buf);
1134                         if (GetServerStatus(Buf, NULL) == 4) {
1135                                 if (saving_to_drafts) {
1136                                         if (  (havebstr("recp"))
1137                                               || (havebstr("cc"  ))
1138                                               || (havebstr("bcc" )) ) {
1139                                                 /* save recipient headers or room to post to */
1140                                                 serv_printf("To: %s", ChrPtr(Recp));
1141                                                 serv_printf("Cc: %s", ChrPtr(Cc));
1142                                                 serv_printf("Bcc: %s", ChrPtr(Bcc));
1143                                         } else {
1144                                                 serv_printf("X-Citadel-Room: %s", ChrPtr(WCC->CurRoom.name));
1145                                         }
1146                                 }
1147                                 post_mime_to_server();
1148                                 if (saving_to_drafts) {
1149                                         AppendImportantMessage(_("Message has been saved to Drafts.\n"), -1);
1150                                         gotoroom(WCC->CurRoom.name);
1151                                         fixview();
1152                                         readloop(readnew, eUseDefault);
1153                                         FreeStrBuf(&Buf);
1154                                         return;
1155                                 } else if (  (havebstr("recp"))
1156                                              || (havebstr("cc"  ))
1157                                              || (havebstr("bcc" ))
1158                                         ) {
1159                                         AppendImportantMessage(_("Message has been sent.\n"), -1);
1160                                 }
1161                                 else {
1162                                         AppendImportantMessage(_("Message has been posted.\n"), -1);
1163                                 }
1164                                 dont_post = lbstr("postseq");
1165                         } else {
1166                                 syslog(9, "%s:%d: server post error: %s", __FILE__, __LINE__, ChrPtr(Buf) + 4);
1167                                 AppendImportantMessage(ChrPtr(Buf) + 4, StrLength(Buf) - 4);
1168                                 display_enter();
1169                                 if (saving_to_drafts) gotoroom(WCC->CurRoom.name);
1170                                 FreeStrBuf(&Recp);
1171                                 FreeStrBuf(&Buf);
1172                                 FreeStrBuf(&Cc);
1173                                 FreeStrBuf(&Bcc);
1174                                 return;
1175                         }
1176                 }
1177                 FreeStrBuf(&Recp);
1178                 FreeStrBuf(&Buf);
1179                 FreeStrBuf(&Cc);
1180                 FreeStrBuf(&Bcc);
1181         }
1182
1183         DeleteHash(&WCC->attachments);
1184
1185         /*
1186          *  We may have been supplied with instructions regarding the location
1187          *  to which we must return after posting.  If found, go there.
1188          */
1189         if (havebstr("return_to")) {
1190                 http_redirect(bstr("return_to"));
1191         }
1192         /*
1193          *  If we were editing a page in a wiki room, go to that page now.
1194          */
1195         else if (havebstr("page")) {
1196                 snprintf(buf, sizeof buf, "wiki?page=%s", bstr("page"));
1197                 http_redirect(buf);
1198         }
1199         /*
1200          *  Otherwise, just go to the "read messages" loop.
1201          */
1202         else {
1203                 fixview();
1204                 readloop(readnew, eUseDefault);
1205         }
1206 }
1207
1208
1209 /*
1210  * Client is uploading an attachment
1211  */
1212 void upload_attachment(void) {
1213         wcsession *WCC = WC;
1214         const char *pch;
1215         int n;
1216         const char *newn;
1217         long newnlen;
1218         void *v;
1219         wc_mime_attachment *att;
1220
1221         syslog(9, "upload_attachment()\n");
1222         wc_printf("upload_attachment()<br>\n");
1223
1224         if (WCC->upload_length <= 0) {
1225                 syslog(9, "ERROR no attachment was uploaded\n");
1226                 wc_printf("ERROR no attachment was uploaded<br>\n");
1227                 return;
1228         }
1229
1230         syslog(9, "Client is uploading %d bytes\n", WCC->upload_length);
1231         wc_printf("Client is uploading %d bytes<br>\n", WCC->upload_length);
1232         att = malloc(sizeof(wc_mime_attachment));
1233         memset(att, 0, sizeof(wc_mime_attachment ));
1234         att->length = WCC->upload_length;
1235         att->ContentType = NewStrBufPlain(WCC->upload_content_type, -1);
1236         att->FileName = NewStrBufDup(WCC->upload_filename);
1237         
1238         if (WCC->attachments == NULL) {
1239                 WCC->attachments = NewHash(1, Flathash);
1240         }
1241
1242         /* And add it to the list. */
1243         n = 0;
1244         if ((GetCount(WCC->attachments) > 0) && 
1245             GetHashAt(WCC->attachments, 
1246                       GetCount(WCC->attachments) -1, 
1247                       &newnlen, &newn, &v))
1248             n = *((int*) newn) + 1;
1249         Put(WCC->attachments, IKEY(n), att, DestroyMime);
1250
1251         /*
1252          * Mozilla sends a simple filename, which is what we want,
1253          * but Satan's Browser sends an entire pathname.  Reduce
1254          * the path to just a filename if we need to.
1255          */
1256         pch = strrchr(ChrPtr(att->FileName), '/');
1257         if (pch != NULL) {
1258                 StrBufCutLeft(att->FileName, pch - ChrPtr(att->FileName) + 1);
1259         }
1260         pch = strrchr(ChrPtr(att->FileName), '\\');
1261         if (pch != NULL) {
1262                 StrBufCutLeft(att->FileName, pch - ChrPtr(att->FileName) + 1);
1263         }
1264
1265         /*
1266          * Transfer control of this memory from the upload struct
1267          * to the attachment struct.
1268          */
1269         att->Data = WCC->upload;
1270         WCC->upload = NULL;
1271         WCC->upload_length = 0;
1272 }
1273
1274
1275 /*
1276  * Remove an attachment from the message currently being composed.
1277  *
1278  * Currently we identify the attachment to be removed by its filename.
1279  * There is probably a better way to do this.
1280  */
1281 void remove_attachment(void) {
1282         wcsession *WCC = WC;
1283         wc_mime_attachment *att;
1284         void *vAtt;
1285         StrBuf *WhichAttachment;
1286         HashPos *at;
1287         long len;
1288         const char *key;
1289
1290         WhichAttachment = NewStrBufDup(sbstr("which_attachment"));
1291         StrBufUnescape(WhichAttachment, 0);
1292         at = GetNewHashPos(WCC->attachments, 0);
1293         do {
1294                 GetHashPos(WCC->attachments, at, &len, &key, &vAtt);
1295         
1296                 att = (wc_mime_attachment*) vAtt;
1297                 if ((att != NULL) && 
1298                     (strcmp(ChrPtr(WhichAttachment), 
1299                             ChrPtr(att->FileName)   ) == 0))
1300                 {
1301                         DeleteEntryFromHash(WCC->attachments, at);
1302                         break;
1303                 }
1304         }
1305         while (NextHashPos(WCC->attachments, at));
1306         FreeStrBuf(&WhichAttachment);
1307         wc_printf("remove_attachment() completed\n");
1308 }
1309
1310
1311 long FourHash(const char *key, long length) 
1312 {
1313         int i;
1314         long ret = 0;
1315         const unsigned char *ptr = (const unsigned char*)key;
1316
1317         for (i = 0; i < 4; i++, ptr ++) 
1318                 ret = (ret << 8) | 
1319                         ( ((*ptr >= 'a') &&
1320                            (*ptr <= 'z'))? 
1321                           *ptr - 'a' + 'A': 
1322                           *ptr);
1323
1324         return ret;
1325 }
1326
1327 long l_subj;
1328 long l_wefw;
1329 long l_msgn;
1330 long l_from;
1331 long l_rcpt;
1332 long l_cccc;
1333 long l_replyto;
1334 long l_node;
1335 long l_rfca;
1336
1337 const char *ReplyToModeStrings [3] = {
1338         "reply",
1339         "replyalle",
1340         "forward"
1341 };
1342 typedef enum _eReplyToNodes {
1343         eReply,
1344         eReplyAll,
1345         eForward
1346 }eReplyToNodes;
1347         
1348 /*
1349  * display the message entry screen
1350  */
1351 void display_enter(void)
1352 {
1353         const char *ReplyingModeStr;
1354         eReplyToNodes ReplyMode = eReply;
1355         StrBuf *Line;
1356         long Result;
1357         int rc;
1358         const StrBuf *display_name = NULL;
1359         int recipient_required = 0;
1360         int subject_required = 0;
1361         int is_anonymous = 0;
1362         wcsession *WCC = WC;
1363         int i = 0;
1364         long replying_to;
1365
1366         if (havebstr("force_room")) {
1367                 gotoroom(sbstr("force_room"));
1368         }
1369
1370         display_name = sbstr("display_name");
1371         if (!strcmp(ChrPtr(display_name), "__ANONYMOUS__")) {
1372                 display_name = NULL;
1373                 is_anonymous = 1;
1374         }
1375
1376         /*
1377          * First, do we have permission to enter messages in this room at all?
1378          */
1379         Line = NewStrBuf();
1380         serv_puts("ENT0 0");
1381         StrBuf_ServGetln(Line);
1382         rc = GetServerStatusMsg(Line, &Result, 0, 2);
1383
1384         if (Result == 570) {            /* 570 means that we need a recipient here */
1385                 recipient_required = 1;
1386         }
1387         else if (rc != 2) {             /* Any other error means that we cannot continue */
1388                 rc = GetServerStatusMsg(Line, &Result, 0, 2);
1389                 fixview();
1390                 readloop(readnew, eUseDefault);
1391                 FreeStrBuf(&Line);
1392                 return;
1393         }
1394
1395         /* Is the server strongly recommending that the user enter a message subject? */
1396         if (StrLength(Line) > 4) {
1397                 subject_required = extract_int(ChrPtr(Line) + 4, 1);
1398         }
1399
1400         /*
1401          * Are we perhaps in an address book view?  If so, then an "enter
1402          * message" command really means "add new entry."
1403          */
1404         if (WCC->CurRoom.defview == VIEW_ADDRESSBOOK) {
1405                 do_edit_vcard(-1, "", NULL, NULL, "",  ChrPtr(WCC->CurRoom.name));
1406                 FreeStrBuf(&Line);
1407                 return;
1408         }
1409
1410         /*
1411          * Are we perhaps in a calendar room?  If so, then an "enter
1412          * message" command really means "add new calendar item."
1413          */
1414         if (WCC->CurRoom.defview == VIEW_CALENDAR) {
1415                 display_edit_event();
1416                 FreeStrBuf(&Line);
1417                 return;
1418         }
1419
1420         /*
1421          * Are we perhaps in a tasks view?  If so, then an "enter
1422          * message" command really means "add new task."
1423          */
1424         if (WCC->CurRoom.defview == VIEW_TASKS) {
1425                 display_edit_task();
1426                 FreeStrBuf(&Line);
1427                 return;
1428         }
1429
1430
1431         ReplyingModeStr = bstr("replying_mode");
1432         if (ReplyingModeStr != NULL) for (i = 0; i < 3; i++) {
1433                         if (strcmp(ReplyingModeStr, ReplyToModeStrings[i]) == 0) {
1434                                 ReplyMode = (eReplyToNodes) i;
1435                                 break;
1436                         }
1437                 }
1438                 
1439
1440         /*
1441          * If the "replying_to" variable is set, it refers to a message
1442          * number from which we must extract some header fields...
1443          */
1444         replying_to = lbstr("replying_to");
1445         if (replying_to > 0) {
1446                 long len;
1447                 StrBuf *wefw = NULL;
1448                 StrBuf *msgn = NULL;
1449                 StrBuf *from = NULL;
1450                 StrBuf *node = NULL;
1451                 StrBuf *rfca = NULL;
1452                 StrBuf *rcpt = NULL;
1453                 StrBuf *cccc = NULL;
1454                 StrBuf *replyto = NULL;
1455                 serv_printf("MSG0 %ld|1", replying_to); 
1456
1457                 StrBuf_ServGetln(Line);
1458                 if (GetServerStatusMsg(Line, NULL, 0, 0) == 1)
1459                         while (len = StrBuf_ServGetln(Line),
1460                                (len >= 0) && 
1461                                ((len != 3)  ||
1462                                 strcmp(ChrPtr(Line), "000")))
1463                         {
1464                                 long which = 0;
1465                                 if ((StrLength(Line) > 4) && 
1466                                     (ChrPtr(Line)[4] == '='))
1467                                         which = FourHash(ChrPtr(Line), 4);
1468
1469                                 if (which == l_subj)
1470                                 {
1471                                         StrBuf *subj = NewStrBuf();
1472                                         StrBuf *FlatSubject;
1473
1474                                         if (ReplyMode == eForward) {
1475                                                 if (strncasecmp(ChrPtr(Line) + 5, "Fw:", 3)) {
1476                                                         StrBufAppendBufPlain(subj, HKEY("Fw: "), 0);
1477                                                 }
1478                                         }
1479                                         else {
1480                                                 if (strncasecmp(ChrPtr(Line) + 5, "Re:", 3)) {
1481                                                         StrBufAppendBufPlain(subj, HKEY("Re: "), 0);
1482                                                 }
1483                                         }
1484                                         StrBufAppendBufPlain(subj, 
1485                                                              ChrPtr(Line) + 5, 
1486                                                              StrLength(Line) - 5, 0);
1487                                         FlatSubject = NewStrBufPlain(NULL, StrLength(subj));
1488                                         StrBuf_RFC822_to_Utf8(FlatSubject, subj, NULL, NULL);
1489
1490                                         PutBstr(HKEY("subject"), FlatSubject);
1491                                 }
1492
1493                                 else if (which == l_wefw)
1494                                 {
1495                                         int rrtok;
1496                                         int rrlen;
1497
1498                                         wefw = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1499                                         
1500                                         /* Trim down excessively long lists of thread references.  We eliminate the
1501                                          * second one in the list so that the thread root remains intact.
1502                                          */
1503                                         rrtok = num_tokens(ChrPtr(wefw), '|');
1504                                         rrlen = StrLength(wefw);
1505                                         if ( ((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10) ) {
1506                                                 StrBufRemove_token(wefw, 1, '|');
1507                                         }
1508                                 }
1509
1510                                 else if (which == l_msgn) {
1511                                         msgn = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1512                                 }
1513
1514                                 else if (which == l_from) {
1515                                         StrBuf *FlatFrom;
1516                                         from = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1517                                         FlatFrom = NewStrBufPlain(NULL, StrLength(from));
1518                                         StrBuf_RFC822_to_Utf8(FlatFrom, from, NULL, NULL);
1519                                         FreeStrBuf(&from);
1520                                         from = FlatFrom;
1521                                         for (i=0; i<StrLength(from); ++i) {
1522                                                 if (ChrPtr(from)[i] == ',')
1523                                                         StrBufPeek(from, NULL, i, ' ');
1524                                         }
1525                                 }
1526                                 
1527                                 else if (which == l_rcpt) {
1528                                         rcpt = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1529                                 }
1530                                 
1531                                 else if (which == l_cccc) {
1532                                         cccc = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1533                                 }
1534                                 
1535                                 else if (which == l_node) {
1536                                         node = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1537                                 }
1538                                 else if (which == l_replyto) {
1539                                         replyto = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1540                                 }                               
1541                                 else if (which == l_rfca) {
1542                                         StrBuf *FlatRFCA;
1543                                         rfca = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1544                                         FlatRFCA = NewStrBufPlain(NULL, StrLength(rfca));
1545                                         StrBuf_RFC822_to_Utf8(FlatRFCA, rfca, NULL, NULL);
1546                                         FreeStrBuf(&rfca);
1547                                         rfca = FlatRFCA;
1548                                 }
1549                         }
1550
1551
1552                 if (StrLength(wefw) + StrLength(msgn) > 0) {
1553                         StrBuf *refs = NewStrBuf();
1554                         if (StrLength(wefw) > 0) {
1555                                 StrBufAppendBuf(refs, wefw, 0);
1556                         }
1557                         if ( (StrLength(wefw) > 0) && 
1558                              (StrLength(msgn) > 0) ) 
1559                         {
1560                                 StrBufAppendBufPlain(refs, HKEY("|"), 0);
1561                         }
1562                         if (StrLength(msgn) > 0) {
1563                                 StrBufAppendBuf(refs, msgn, 0);
1564                         }
1565                         PutBstr(HKEY("references"), refs);
1566                 }
1567
1568                 /*
1569                  * If this is a Reply or a ReplyAll, copy the sender's email into the To: field
1570                  */
1571                 if ((ReplyMode == eReply) || (ReplyMode == eReplyAll))
1572                 {
1573                         StrBuf *to_rcpt;
1574                         if ((StrLength(replyto) > 0) && (ReplyMode == eReplyAll)) {
1575                                 to_rcpt = NewStrBuf();
1576                                 StrBufAppendBuf(to_rcpt, replyto, 0);
1577                         }
1578                         else if (StrLength(rfca) > 0) {
1579                                 to_rcpt = NewStrBuf();
1580                                 StrBufAppendBuf(to_rcpt, from, 0);
1581                                 StrBufAppendBufPlain(to_rcpt, HKEY(" <"), 0);
1582                                 StrBufAppendBuf(to_rcpt, rfca, 0);
1583                                 StrBufAppendBufPlain(to_rcpt, HKEY(">"), 0);
1584                         }
1585                         else {
1586                                 to_rcpt =  from;
1587                                 from = NULL;
1588                                 if (    (StrLength(node) > 0)
1589                                         && (strcasecmp(ChrPtr(node), ChrPtr(WCC->serv_info->serv_nodename)))
1590                                 ) {
1591                                         StrBufAppendBufPlain(to_rcpt, HKEY(" @ "), 0);
1592                                         StrBufAppendBuf(to_rcpt, node, 0);
1593                                 }
1594                         }
1595                         PutBstr(HKEY("recp"), to_rcpt);
1596                 }
1597
1598                 /*
1599                  * Only if this is a ReplyAll, copy all recipients into the Cc: field
1600                  */
1601                 if (ReplyMode == eReplyAll)
1602                 {
1603                         StrBuf *cc_rcpt = rcpt;
1604                         rcpt = NULL;
1605                         if ((StrLength(cccc) > 0) && (StrLength(replyto) == 0))
1606                         {
1607                                 if (cc_rcpt != NULL)  {
1608                                         StrBufAppendPrintf(cc_rcpt, ", ");
1609                                         StrBufAppendBuf(cc_rcpt, cccc, 0);
1610                                 } else {
1611                                         cc_rcpt = cccc;
1612                                         cccc = NULL;
1613                                 }
1614                         }
1615                         if (cc_rcpt != NULL)
1616                                 PutBstr(HKEY("cc"), cc_rcpt);
1617                 }
1618                 FreeStrBuf(&wefw);
1619                 FreeStrBuf(&msgn);
1620                 FreeStrBuf(&from);
1621                 FreeStrBuf(&node);
1622                 FreeStrBuf(&rfca);
1623                 FreeStrBuf(&rcpt);
1624                 FreeStrBuf(&cccc);
1625         }
1626         FreeStrBuf(&Line);
1627         /*
1628          * Otherwise proceed normally.
1629          * Do a custom room banner with no navbar...
1630          */
1631
1632         if (recipient_required) {
1633                 const StrBuf *Recp = NULL; 
1634                 const StrBuf *Cc = NULL;
1635                 const StrBuf *Bcc = NULL;
1636                 char *wikipage = NULL;
1637                 StrBuf *CmdBuf = NULL;
1638                 const char CMD[] = "ENT0 0|%s|%d|0||%s||%s|%s|%s";
1639                 
1640                 Recp = sbstr("recp");
1641                 Cc = sbstr("cc");
1642                 Bcc = sbstr("bcc");
1643                 wikipage = strdup(bstr("page"));
1644                 str_wiki_index(wikipage);
1645                 
1646                 CmdBuf = NewStrBufPlain(NULL, 
1647                                         sizeof (CMD) + 
1648                                         StrLength(Recp) + 
1649                                         StrLength(display_name) +
1650                                         StrLength(Cc) +
1651                                         StrLength(Bcc) + 
1652                                         strlen(wikipage));
1653
1654                 StrBufPrintf(CmdBuf, 
1655                              CMD,
1656                              ChrPtr(Recp), 
1657                              is_anonymous,
1658                              ChrPtr(display_name),
1659                              ChrPtr(Cc), 
1660                              ChrPtr(Bcc), 
1661                              wikipage
1662                 );
1663                 serv_puts(ChrPtr(CmdBuf));
1664                 StrBuf_ServGetln(CmdBuf);
1665                 free(wikipage);
1666
1667                 rc = GetServerStatusMsg(CmdBuf, &Result, 0, 0);
1668
1669                 if (    (Result == 570)         /* invalid or missing recipient(s) */
1670                         || (Result == 550)      /* higher access required to send Internet mail */
1671                 ) {
1672                         /* These errors will have been displayed and are excusable */
1673                 }
1674                 else if (rc != 2) {     /* Any other error means that we cannot continue */
1675                         AppendImportantMessage(ChrPtr(CmdBuf) + 4, StrLength(CmdBuf) - 4);
1676                         FreeStrBuf(&CmdBuf);
1677                         fixview();
1678                         readloop(readnew, eUseDefault);
1679                         return;
1680                 }
1681                 FreeStrBuf(&CmdBuf);
1682         }
1683         if (recipient_required)
1684                 PutBstr(HKEY("__RCPTREQUIRED"), NewStrBufPlain(HKEY("1")));
1685         if (recipient_required || subject_required)
1686                 PutBstr(HKEY("__SUBJREQUIRED"), NewStrBufPlain(HKEY("1")));
1687
1688         begin_burst();
1689         output_headers(1, 0, 0, 0, 1, 0);
1690         DoTemplate(HKEY("edit_message"), NULL, &NoCtx);
1691         end_burst();
1692
1693         return;
1694 }
1695
1696 /*
1697  * delete a message
1698  */
1699 void delete_msg(void)
1700 {
1701         long msgid;
1702         StrBuf *Line;
1703         
1704         msgid = lbstr("msgid");
1705         Line = NewStrBuf();
1706         if ((WC->CurRoom.RAFlags & UA_ISTRASH) != 0) {  /* Delete from Trash is a real delete */
1707                 serv_printf("DELE %ld", msgid); 
1708         }
1709         else {                  /* Otherwise move it to Trash */
1710                 serv_printf("MOVE %ld|_TRASH_|0", msgid);
1711         }
1712
1713         StrBuf_ServGetln(Line);
1714         GetServerStatusMsg(Line, NULL, 1, 0);
1715
1716         fixview();
1717
1718         readloop(readnew, eUseDefault);
1719 }
1720
1721
1722 /*
1723  * move a message to another room
1724  */
1725 void move_msg(void)
1726 {
1727         long msgid;
1728
1729         msgid = lbstr("msgid");
1730
1731         if (havebstr("move_button")) {
1732                 StrBuf *Line;
1733                 serv_printf("MOVE %ld|%s", msgid, bstr("target_room"));
1734                 Line = NewStrBuf();
1735                 StrBuf_ServGetln(Line);
1736                 GetServerStatusMsg(Line, NULL, 1, 0);
1737                 FreeStrBuf(&Line);
1738         } else {
1739                 AppendImportantMessage(_("The message was not moved."), -1);
1740         }
1741
1742         fixview();
1743         readloop(readnew, eUseDefault);
1744 }
1745
1746
1747
1748 /*
1749  * Generic function to output an arbitrary MIME attachment from
1750  * message being composed
1751  *
1752  * partnum              The MIME part to be output
1753  * filename             Fake filename to give
1754  * force_download       Nonzero to force set the Content-Type: header to "application/octet-stream"
1755  */
1756 void postpart(StrBuf *partnum, StrBuf *filename, int force_download)
1757 {
1758         void *vPart;
1759         StrBuf *content_type;
1760         wc_mime_attachment *part;
1761         int i;
1762
1763         i = StrToi(partnum);
1764         if (GetHash(WC->attachments, IKEY(i), &vPart) &&
1765             (vPart != NULL)) {
1766                 part = (wc_mime_attachment*) vPart;
1767                 if (force_download) {
1768                         content_type = NewStrBufPlain(HKEY("application/octet-stream"));
1769                 }
1770                 else {
1771                         content_type = NewStrBufDup(part->ContentType);
1772                 }
1773                 StrBufAppendBuf(WC->WBuf, part->Data, 0);
1774                 http_transmit_thing(ChrPtr(content_type), 0);
1775         } else {
1776                 hprintf("HTTP/1.1 404 %s\n", ChrPtr(partnum));
1777                 output_headers(0, 0, 0, 0, 0, 0);
1778                 hprintf("Content-Type: text/plain\r\n");
1779                 begin_burst();
1780                 wc_printf(_("An error occurred while retrieving this part: %s/%s\n"), 
1781                         ChrPtr(partnum), ChrPtr(filename));
1782                 end_burst();
1783         }
1784         FreeStrBuf(&content_type);
1785 }
1786
1787
1788 /*
1789  * Generic function to output an arbitrary MIME part from an arbitrary
1790  * message number on the server.
1791  *
1792  * msgnum               Number of the item on the citadel server
1793  * partnum              The MIME part to be output
1794  * force_download       Nonzero to force set the Content-Type: header to "application/octet-stream"
1795  */
1796 void mimepart(int force_download)
1797 {
1798         long msgnum;
1799         long ErrorDetail;
1800         StrBuf *att;
1801         wcsession *WCC = WC;
1802         StrBuf *Buf;
1803         off_t bytes;
1804         StrBuf *ContentType = NewStrBufPlain(HKEY("application/octet-stream"));
1805         const char *CT;
1806
1807         att = Buf = NewStrBuf();
1808         msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
1809         StrBufExtract_token(att, WCC->Hdr->HR.ReqLine, 1, '/');
1810
1811         serv_printf("OPNA %ld|%s", msgnum, ChrPtr(att));
1812         StrBuf_ServGetln(Buf);
1813         if (GetServerStatus(Buf, &ErrorDetail) == 2) {
1814                 StrBufCutLeft(Buf, 4);
1815                 bytes = StrBufExtract_long(Buf, 0, '|');
1816                 if (!force_download) {
1817                         StrBufExtract_token(ContentType, Buf, 3, '|');
1818                 }
1819
1820                 serv_read_binary(WCC->WBuf, bytes, Buf);
1821                 serv_puts("CLOS");
1822                 StrBuf_ServGetln(Buf);
1823                 CT = ChrPtr(ContentType);
1824
1825                 if (!force_download) {
1826                         if (!strcasecmp(ChrPtr(ContentType), "application/octet-stream")) {
1827                                 StrBufExtract_token(Buf, WCC->Hdr->HR.ReqLine, 2, '/');
1828                                 CT = GuessMimeByFilename(SKEY(Buf));
1829                         }
1830                         if (!strcasecmp(ChrPtr(ContentType), "application/octet-stream")) {
1831                                 CT = GuessMimeType(SKEY(WCC->WBuf));
1832                         }
1833                 }
1834                 http_transmit_thing(CT, 0);
1835         } else {
1836                 StrBufCutLeft(Buf, 4);
1837                 switch (ErrorDetail) {
1838                 default:
1839                 case ERROR + MESSAGE_NOT_FOUND:
1840                         hprintf("HTTP/1.1 404 %s\n", ChrPtr(Buf));
1841                         break;
1842                 case ERROR + NOT_LOGGED_IN:
1843                         hprintf("HTTP/1.1 401 %s\n", ChrPtr(Buf));
1844                         break;
1845
1846                 case ERROR + HIGHER_ACCESS_REQUIRED:
1847                         hprintf("HTTP/1.1 403 %s\n", ChrPtr(Buf));
1848                         break;
1849                 case ERROR + INTERNAL_ERROR:
1850                 case ERROR + TOO_BIG:
1851                         hprintf("HTTP/1.1 500 %s\n", ChrPtr(Buf));
1852                         break;
1853                 }
1854                 output_headers(0, 0, 0, 0, 0, 0);
1855                 hprintf("Content-Type: text/plain\r\n");
1856                 begin_burst();
1857                 wc_printf(_("An error occurred while retrieving this part: %s\n"), 
1858                         ChrPtr(Buf));
1859                 end_burst();
1860         }
1861         FreeStrBuf(&ContentType);
1862         FreeStrBuf(&Buf);
1863 }
1864
1865
1866 /*
1867  * Read any MIME part of a message, from the server, into memory.
1868  */
1869 StrBuf *load_mimepart(long msgnum, char *partnum)
1870 {
1871         off_t bytes;
1872         StrBuf *Buf;
1873         
1874         Buf = NewStrBuf();
1875         serv_printf("DLAT %ld|%s", msgnum, partnum);
1876         StrBuf_ServGetln(Buf);
1877         if (GetServerStatus(Buf, NULL) == 6) {
1878                 StrBufCutLeft(Buf, 4);
1879                 bytes = StrBufExtract_long(Buf, 0, '|');
1880                 FreeStrBuf(&Buf);
1881                 Buf = NewStrBuf();
1882                 StrBuf_ServGetBLOBBuffered(Buf, bytes);
1883                 return(Buf);
1884         }
1885         else {
1886                 FreeStrBuf(&Buf);
1887                 return(NULL);
1888         }
1889 }
1890
1891 /*
1892  * Read any MIME part of a message, from the server, into memory.
1893  */
1894 void MimeLoadData(wc_mime_attachment *Mime)
1895 {
1896         StrBuf *Buf;
1897         const char *Ptr;
1898         off_t bytes;
1899         /* TODO: is there a chance the content type is different from the one we know? */
1900
1901         serv_printf("DLAT %ld|%s", Mime->msgnum, ChrPtr(Mime->PartNum));
1902         Buf = NewStrBuf();
1903         StrBuf_ServGetln(Buf);
1904         if (GetServerStatus(Buf, NULL) == 6) {
1905                 Ptr = &(ChrPtr(Buf)[4]);
1906                 bytes = StrBufExtractNext_long(Buf, &Ptr, '|');
1907                 StrBufSkip_NTokenS(Buf, &Ptr, '|', 3);  /* filename, cbtype, mimetype */
1908                 if (Mime->Charset == NULL) Mime->Charset = NewStrBuf();
1909                 StrBufExtract_NextToken(Mime->Charset, Buf, &Ptr, '|');
1910                 
1911                 if (Mime->Data == NULL)
1912                         Mime->Data = NewStrBufPlain(NULL, bytes);
1913                 StrBuf_ServGetBLOBBuffered(Mime->Data, bytes);
1914         }
1915         else {
1916                 FlushStrBuf(Mime->Data);
1917                 /* TODO XImportant message */
1918         }
1919         FreeStrBuf(&Buf);
1920 }
1921
1922
1923 void view_mimepart(void) {
1924         mimepart(0);
1925 }
1926
1927 void download_mimepart(void) {
1928         mimepart(1);
1929 }
1930
1931 void view_postpart(void) {
1932         StrBuf *filename = NewStrBuf();
1933         StrBuf *partnum = NewStrBuf();
1934
1935         StrBufExtract_token(partnum, WC->Hdr->HR.ReqLine, 0, '/');
1936         StrBufExtract_token(filename, WC->Hdr->HR.ReqLine, 1, '/');
1937
1938         postpart(partnum, filename, 0);
1939
1940         FreeStrBuf(&filename);
1941         FreeStrBuf(&partnum);
1942 }
1943
1944 void download_postpart(void) {
1945         StrBuf *filename = NewStrBuf();
1946         StrBuf *partnum = NewStrBuf();
1947
1948         StrBufExtract_token(partnum, WC->Hdr->HR.ReqLine, 0, '/');
1949         StrBufExtract_token(filename, WC->Hdr->HR.ReqLine, 1, '/');
1950
1951         postpart(partnum, filename, 1);
1952
1953         FreeStrBuf(&filename);
1954         FreeStrBuf(&partnum);
1955 }
1956
1957
1958
1959 void show_num_attachments(void) {
1960         wc_printf("%d", GetCount(WC->attachments));
1961 }
1962
1963
1964 void h_readnew(void) { readloop(readnew, eUseDefault);}
1965 void h_readold(void) { readloop(readold, eUseDefault);}
1966 void h_readfwd(void) { readloop(readfwd, eUseDefault);}
1967 void h_headers(void) { readloop(headers, eUseDefault);}
1968 void h_do_search(void) { readloop(do_search, eUseDefault);}
1969 void h_readgt(void) { readloop(readgt, eUseDefault);}
1970 void h_readlt(void) { readloop(readlt, eUseDefault);}
1971
1972
1973
1974 /* Output message list in JSON format */
1975 void jsonMessageList(void) {
1976         StrBuf *View = NewStrBuf();
1977         const StrBuf *room = sbstr("room");
1978         long oper = (havebstr("query")) ? do_search : readnew;
1979         StrBufPrintf(View, "%d", VIEW_JSON_LIST);
1980         putbstr("view", View);; 
1981         gotoroom(room);
1982         readloop(oper, eUseDefault);
1983 }
1984
1985 void RegisterReadLoopHandlerset(
1986         int RoomType,
1987         GetParamsGetServerCall_func GetParamsGetServerCall,
1988         PrintViewHeader_func PrintPageHeader,
1989         PrintViewHeader_func PrintViewHeader,
1990         load_msg_ptrs_detailheaders LH,
1991         LoadMsgFromServer_func LoadMsgFromServer,
1992         RenderView_or_Tail_func RenderView_or_Tail,
1993         View_Cleanup_func ViewCleanup
1994         )
1995 {
1996         RoomRenderer *Handler;
1997
1998         Handler = (RoomRenderer*) malloc(sizeof(RoomRenderer));
1999
2000         Handler->RoomType = RoomType;
2001         Handler->GetParamsGetServerCall = GetParamsGetServerCall;
2002         Handler->PrintPageHeader = PrintPageHeader;
2003         Handler->PrintViewHeader = PrintViewHeader;
2004         Handler->LoadMsgFromServer = LoadMsgFromServer;
2005         Handler->RenderView_or_Tail = RenderView_or_Tail;
2006         Handler->ViewCleanup = ViewCleanup;
2007         Handler->LHParse = LH;
2008
2009         Put(ReadLoopHandler, IKEY(RoomType), Handler, NULL);
2010 }
2011
2012 void 
2013 InitModule_MSG
2014 (void)
2015 {
2016         RegisterPreference("use_sig",
2017                            _("Attach signature to email messages?"), 
2018                            PRF_YESNO, 
2019                            NULL);
2020         RegisterPreference("signature", _("Use this signature:"), PRF_QP_STRING, NULL);
2021         RegisterPreference("default_header_charset", 
2022                            _("Default character set for email headers:"), 
2023                            PRF_STRING, 
2024                            NULL);
2025         RegisterPreference("defaultfrom", _("Preferred email address"), PRF_STRING, NULL);
2026         RegisterPreference("defaultname", 
2027                            _("Preferred display name for email messages"), 
2028                            PRF_STRING, 
2029                            NULL);
2030         RegisterPreference("defaulthandle", 
2031                            _("Preferred display name for bulletin board posts"), 
2032                            PRF_STRING, 
2033                            NULL);
2034         RegisterPreference("mailbox",_("Mailbox view mode"), PRF_STRING, NULL);
2035
2036         WebcitAddUrlHandler(HKEY("readnew"), "", 0, h_readnew, ANONYMOUS|NEED_URL);
2037         WebcitAddUrlHandler(HKEY("readold"), "", 0, h_readold, ANONYMOUS|NEED_URL);
2038         WebcitAddUrlHandler(HKEY("readfwd"), "", 0, h_readfwd, ANONYMOUS|NEED_URL);
2039         WebcitAddUrlHandler(HKEY("headers"), "", 0, h_headers, NEED_URL);
2040         WebcitAddUrlHandler(HKEY("readgt"), "", 0, h_readgt, ANONYMOUS|NEED_URL);
2041         WebcitAddUrlHandler(HKEY("readlt"), "", 0, h_readlt, ANONYMOUS|NEED_URL);
2042         WebcitAddUrlHandler(HKEY("do_search"), "", 0, h_do_search, 0);
2043         WebcitAddUrlHandler(HKEY("display_enter"), "", 0, display_enter, 0);
2044         WebcitAddUrlHandler(HKEY("post"), "", 0, post_message, PROHIBIT_STARTPAGE);
2045         WebcitAddUrlHandler(HKEY("move_msg"), "", 0, move_msg, PROHIBIT_STARTPAGE);
2046         WebcitAddUrlHandler(HKEY("delete_msg"), "", 0, delete_msg, PROHIBIT_STARTPAGE);
2047         WebcitAddUrlHandler(HKEY("msg"), "", 0, embed_message, NEED_URL);
2048         WebcitAddUrlHandler(HKEY("message"), "", 0, handle_one_message, NEED_URL|XHTTP_COMMANDS|COOKIEUNNEEDED|FORCE_SESSIONCLOSE);
2049         WebcitAddUrlHandler(HKEY("printmsg"), "", 0, print_message, NEED_URL);
2050         WebcitAddUrlHandler(HKEY("msgheaders"), "", 0, display_headers, NEED_URL);
2051
2052         WebcitAddUrlHandler(HKEY("mimepart"), "", 0, view_mimepart, NEED_URL);
2053         WebcitAddUrlHandler(HKEY("mimepart_download"), "", 0, download_mimepart, NEED_URL);
2054         WebcitAddUrlHandler(HKEY("postpart"), "", 0, view_postpart, NEED_URL|PROHIBIT_STARTPAGE);
2055         WebcitAddUrlHandler(HKEY("postpart_download"), "", 0, download_postpart, NEED_URL|PROHIBIT_STARTPAGE);
2056         WebcitAddUrlHandler(HKEY("upload_attachment"), "", 0, upload_attachment, AJAX);
2057         WebcitAddUrlHandler(HKEY("remove_attachment"), "", 0, remove_attachment, AJAX);
2058         WebcitAddUrlHandler(HKEY("show_num_attachments"), "", 0, show_num_attachments, AJAX);
2059
2060         /* json */
2061         WebcitAddUrlHandler(HKEY("roommsgs"), "", 0, jsonMessageList,0);
2062
2063         l_subj = FourHash("subj", 4);
2064         l_wefw = FourHash("wefw", 4);
2065         l_msgn = FourHash("msgn", 4);
2066         l_from = FourHash("from", 4);
2067         l_rcpt = FourHash("rcpt", 4);
2068         l_cccc = FourHash("cccc", 4);
2069         l_replyto = FourHash("rep2", 4);
2070         l_node = FourHash("node", 4);
2071         l_rfca = FourHash("rfca", 4);
2072
2073         return ;
2074 }
2075
2076 void
2077 SessionDetachModule_MSG
2078 (wcsession *sess)
2079 {
2080         DeleteHash(&sess->summ);
2081 }