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