405656eab269ec22794ea6c7c5b5d080fc9726c1
[citadel] / 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) 
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(NULL, &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);
361                 else 
362                         read_message(WCC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
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);
420                 else 
421                         read_message(WCC->WBuf, HKEY("view_message"), msgnum, NULL, &Mime);
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);
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 long FourHash(const char *key, long length) 
1314 {
1315         int i;
1316         long ret = 0;
1317         const unsigned char *ptr = (const unsigned char*)key;
1318
1319         for (i = 0; i < 4; i++, ptr ++) 
1320                 ret = (ret << 8) | 
1321                         ( ((*ptr >= 'a') &&
1322                            (*ptr <= 'z'))? 
1323                           *ptr - 'a' + 'A': 
1324                           *ptr);
1325
1326         return ret;
1327 }
1328
1329 long l_subj;
1330 long l_wefw;
1331 long l_msgn;
1332 long l_from;
1333 long l_rcpt;
1334 long l_cccc;
1335 long l_replyto;
1336 long l_node;
1337 long l_rfca;
1338 long l_nvto;
1339
1340 const char *ReplyToModeStrings [3] = {
1341         "reply",
1342         "replyall",
1343         "forward"
1344 };
1345 typedef enum _eReplyToNodes {
1346         eReply,
1347         eReplyAll,
1348         eForward
1349 }eReplyToNodes;
1350         
1351 /*
1352  * display the message entry screen
1353  */
1354 void display_enter(void)
1355 {
1356         const char *ReplyingModeStr;
1357         eReplyToNodes ReplyMode = eReply;
1358         StrBuf *Line;
1359         long Result;
1360         int rc;
1361         const StrBuf *display_name = NULL;
1362         int recipient_required = 0;
1363         int subject_required = 0;
1364         int is_anonymous = 0;
1365         wcsession *WCC = WC;
1366         int i = 0;
1367         long replying_to;
1368
1369         if (havebstr("force_room")) {
1370                 gotoroom(sbstr("force_room"));
1371         }
1372
1373         display_name = sbstr("display_name");
1374         if (!strcmp(ChrPtr(display_name), "__ANONYMOUS__")) {
1375                 display_name = NULL;
1376                 is_anonymous = 1;
1377         }
1378
1379         /*
1380          * First, do we have permission to enter messages in this room at all?
1381          */
1382         Line = NewStrBuf();
1383         serv_puts("ENT0 0");
1384         StrBuf_ServGetln(Line);
1385         rc = GetServerStatusMsg(Line, &Result, 0, 2);
1386
1387         if (Result == 570) {            /* 570 means that we need a recipient here */
1388                 recipient_required = 1;
1389         }
1390         else if (rc != 2) {             /* Any other error means that we cannot continue */
1391                 rc = GetServerStatusMsg(Line, &Result, 0, 2);
1392                 fixview();
1393                 readloop(readnew, eUseDefault);
1394                 FreeStrBuf(&Line);
1395                 return;
1396         }
1397
1398         /* Is the server strongly recommending that the user enter a message subject? */
1399         if (StrLength(Line) > 4) {
1400                 subject_required = extract_int(ChrPtr(Line) + 4, 1);
1401         }
1402
1403         /*
1404          * Are we perhaps in an address book view?  If so, then an "enter
1405          * message" command really means "add new entry."
1406          */
1407         if (WCC->CurRoom.defview == VIEW_ADDRESSBOOK) {
1408                 do_edit_vcard(-1, "", NULL, NULL, "",  ChrPtr(WCC->CurRoom.name));
1409                 FreeStrBuf(&Line);
1410                 return;
1411         }
1412
1413         /*
1414          * Are we perhaps in a calendar room?  If so, then an "enter
1415          * message" command really means "add new calendar item."
1416          */
1417         if (WCC->CurRoom.defview == VIEW_CALENDAR) {
1418                 display_edit_event();
1419                 FreeStrBuf(&Line);
1420                 return;
1421         }
1422
1423         /*
1424          * Are we perhaps in a tasks view?  If so, then an "enter
1425          * message" command really means "add new task."
1426          */
1427         if (WCC->CurRoom.defview == VIEW_TASKS) {
1428                 display_edit_task();
1429                 FreeStrBuf(&Line);
1430                 return;
1431         }
1432
1433
1434         ReplyingModeStr = bstr("replying_mode");
1435         if (ReplyingModeStr != NULL) for (i = 0; i < 3; i++) {
1436                         if (strcmp(ReplyingModeStr, ReplyToModeStrings[i]) == 0) {
1437                                 ReplyMode = (eReplyToNodes) i;
1438                                 break;
1439                         }
1440                 }
1441                 
1442
1443         /*
1444          * If the "replying_to" variable is set, it refers to a message
1445          * number from which we must extract some header fields...
1446          */
1447         replying_to = lbstr("replying_to");
1448         if (replying_to > 0) {
1449                 long len;
1450                 StrBuf *wefw = NULL;
1451                 StrBuf *msgn = NULL;
1452                 StrBuf *from = NULL;
1453                 StrBuf *node = NULL;
1454                 StrBuf *rfca = NULL;
1455                 StrBuf *rcpt = NULL;
1456                 StrBuf *cccc = NULL;
1457                 StrBuf *replyto = NULL;
1458                 StrBuf *nvto = NULL;
1459                 serv_printf("MSG0 %ld|1", replying_to); 
1460
1461                 StrBuf_ServGetln(Line);
1462                 if (GetServerStatusMsg(Line, NULL, 0, 0) == 1)
1463                         while (len = StrBuf_ServGetln(Line),
1464                                (len >= 0) && 
1465                                ((len != 3)  ||
1466                                 strcmp(ChrPtr(Line), "000")))
1467                         {
1468                                 long which = 0;
1469                                 if ((StrLength(Line) > 4) && 
1470                                     (ChrPtr(Line)[4] == '='))
1471                                         which = FourHash(ChrPtr(Line), 4);
1472
1473                                 if (which == l_subj)
1474                                 {
1475                                         StrBuf *subj = NewStrBuf();
1476                                         StrBuf *FlatSubject;
1477
1478                                         if (ReplyMode == eForward) {
1479                                                 if (strncasecmp(ChrPtr(Line) + 5, "Fw:", 3)) {
1480                                                         StrBufAppendBufPlain(subj, HKEY("Fw: "), 0);
1481                                                 }
1482                                         }
1483                                         else {
1484                                                 if (strncasecmp(ChrPtr(Line) + 5, "Re:", 3)) {
1485                                                         StrBufAppendBufPlain(subj, HKEY("Re: "), 0);
1486                                                 }
1487                                         }
1488                                         StrBufAppendBufPlain(subj, 
1489                                                              ChrPtr(Line) + 5, 
1490                                                              StrLength(Line) - 5, 0);
1491                                         FlatSubject = NewStrBufPlain(NULL, StrLength(subj));
1492                                         StrBuf_RFC822_to_Utf8(FlatSubject, subj, NULL, NULL);
1493
1494                                         PutBstr(HKEY("subject"), FlatSubject);
1495                                 }
1496
1497                                 else if (which == l_wefw)
1498                                 {
1499                                         int rrtok;
1500                                         int rrlen;
1501
1502                                         wefw = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1503                                         
1504                                         /* Trim down excessively long lists of thread references.  We eliminate the
1505                                          * second one in the list so that the thread root remains intact.
1506                                          */
1507                                         rrtok = num_tokens(ChrPtr(wefw), '|');
1508                                         rrlen = StrLength(wefw);
1509                                         if ( ((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10) ) {
1510                                                 StrBufRemove_token(wefw, 1, '|');
1511                                         }
1512                                 }
1513
1514                                 else if (which == l_msgn) {
1515                                         msgn = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1516                                 }
1517
1518                                 else if (which == l_from) {
1519                                         StrBuf *FlatFrom;
1520                                         from = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1521                                         FlatFrom = NewStrBufPlain(NULL, StrLength(from));
1522                                         StrBuf_RFC822_to_Utf8(FlatFrom, from, NULL, NULL);
1523                                         FreeStrBuf(&from);
1524                                         from = FlatFrom;
1525                                         for (i=0; i<StrLength(from); ++i) {
1526                                                 if (ChrPtr(from)[i] == ',')
1527                                                         StrBufPeek(from, NULL, i, ' ');
1528                                         }
1529                                 }
1530                                 
1531                                 else if (which == l_rcpt) {
1532                                         rcpt = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1533                                 }
1534                                 
1535                                 else if (which == l_cccc) {
1536                                         cccc = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1537                                 }
1538                                 
1539                                 else if (which == l_node) {
1540                                         node = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1541                                 }
1542                                 else if (which == l_replyto) {
1543                                         replyto = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1544                                 }
1545                                 else if (which == l_rfca) {
1546                                         StrBuf *FlatRFCA;
1547                                         rfca = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1548                                         FlatRFCA = NewStrBufPlain(NULL, StrLength(rfca));
1549                                         StrBuf_RFC822_to_Utf8(FlatRFCA, rfca, NULL, NULL);
1550                                         FreeStrBuf(&rfca);
1551                                         rfca = FlatRFCA;
1552                                 }
1553                                 else if (which == l_nvto) {
1554                                         nvto = NewStrBufPlain(ChrPtr(Line) + 5, StrLength(Line) - 5);
1555                                         putbstr("nvto", nvto);
1556                                 }
1557                         }
1558
1559
1560                 if (StrLength(wefw) + StrLength(msgn) > 0) {
1561                         StrBuf *refs = NewStrBuf();
1562                         if (StrLength(wefw) > 0) {
1563                                 StrBufAppendBuf(refs, wefw, 0);
1564                         }
1565                         if ( (StrLength(wefw) > 0) && 
1566                              (StrLength(msgn) > 0) ) 
1567                         {
1568                                 StrBufAppendBufPlain(refs, HKEY("|"), 0);
1569                         }
1570                         if (StrLength(msgn) > 0) {
1571                                 StrBufAppendBuf(refs, msgn, 0);
1572                         }
1573                         PutBstr(HKEY("references"), refs);
1574                 }
1575
1576                 /*
1577                  * If this is a Reply or a ReplyAll, copy the sender's email into the To: field
1578                  */
1579                 if ((ReplyMode == eReply) || (ReplyMode == eReplyAll))
1580                 {
1581                         StrBuf *to_rcpt;
1582                         if ((StrLength(replyto) > 0) && (ReplyMode == eReplyAll)) {
1583                                 to_rcpt = NewStrBuf();
1584                                 StrBufAppendBuf(to_rcpt, replyto, 0);
1585                         }
1586                         else if (StrLength(rfca) > 0) {
1587                                 to_rcpt = NewStrBuf();
1588                                 StrBufAppendBuf(to_rcpt, from, 0);
1589                                 StrBufAppendBufPlain(to_rcpt, HKEY(" <"), 0);
1590                                 StrBufAppendBuf(to_rcpt, rfca, 0);
1591                                 StrBufAppendBufPlain(to_rcpt, HKEY(">"), 0);
1592                         }
1593                         else {
1594                                 to_rcpt =  from;
1595                                 from = NULL;
1596                                 if (    (StrLength(node) > 0)
1597                                         && (strcasecmp(ChrPtr(node), ChrPtr(WCC->serv_info->serv_nodename)))
1598                                 ) {
1599                                         StrBufAppendBufPlain(to_rcpt, HKEY(" @ "), 0);
1600                                         StrBufAppendBuf(to_rcpt, node, 0);
1601                                 }
1602                         }
1603                         PutBstr(HKEY("recp"), to_rcpt);
1604                 }
1605
1606                 /*
1607                  * Only if this is a ReplyAll, copy all recipients into the Cc: field
1608                  */
1609                 if (ReplyMode == eReplyAll)
1610                 {
1611                         StrBuf *cc_rcpt = rcpt;
1612                         rcpt = NULL;
1613                         if ((StrLength(cccc) > 0) && (StrLength(replyto) == 0))
1614                         {
1615                                 if (cc_rcpt != NULL)  {
1616                                         StrBufAppendPrintf(cc_rcpt, ", ");
1617                                         StrBufAppendBuf(cc_rcpt, cccc, 0);
1618                                 } else {
1619                                         cc_rcpt = cccc;
1620                                         cccc = NULL;
1621                                 }
1622                         }
1623                         if (cc_rcpt != NULL)
1624                                 PutBstr(HKEY("cc"), cc_rcpt);
1625                 }
1626                 FreeStrBuf(&wefw);
1627                 FreeStrBuf(&msgn);
1628                 FreeStrBuf(&from);
1629                 FreeStrBuf(&node);
1630                 FreeStrBuf(&rfca);
1631                 FreeStrBuf(&rcpt);
1632                 FreeStrBuf(&cccc);
1633         }
1634         FreeStrBuf(&Line);
1635         /*
1636          * Otherwise proceed normally.
1637          * Do a custom room banner with no navbar...
1638          */
1639
1640         if (recipient_required) {
1641                 const StrBuf *Recp = NULL; 
1642                 const StrBuf *Cc = NULL;
1643                 const StrBuf *Bcc = NULL;
1644                 char *wikipage = NULL;
1645                 StrBuf *CmdBuf = NULL;
1646                 const char CMD[] = "ENT0 0|%s|%d|0||%s||%s|%s|%s";
1647                 
1648                 Recp = sbstr("recp");
1649                 Cc = sbstr("cc");
1650                 Bcc = sbstr("bcc");
1651                 wikipage = strdup(bstr("page"));
1652                 str_wiki_index(wikipage);
1653                 
1654                 CmdBuf = NewStrBufPlain(NULL, 
1655                                         sizeof (CMD) + 
1656                                         StrLength(Recp) + 
1657                                         StrLength(display_name) +
1658                                         StrLength(Cc) +
1659                                         StrLength(Bcc) + 
1660                                         strlen(wikipage));
1661
1662                 StrBufPrintf(CmdBuf, 
1663                              CMD,
1664                              ChrPtr(Recp), 
1665                              is_anonymous,
1666                              ChrPtr(display_name),
1667                              ChrPtr(Cc), 
1668                              ChrPtr(Bcc), 
1669                              wikipage
1670                 );
1671                 serv_puts(ChrPtr(CmdBuf));
1672                 StrBuf_ServGetln(CmdBuf);
1673                 free(wikipage);
1674
1675                 rc = GetServerStatusMsg(CmdBuf, &Result, 0, 0);
1676
1677                 if (    (Result == 570)         /* invalid or missing recipient(s) */
1678                         || (Result == 550)      /* higher access required to send Internet mail */
1679                 ) {
1680                         /* These errors will have been displayed and are excusable */
1681                 }
1682                 else if (rc != 2) {     /* Any other error means that we cannot continue */
1683                         AppendImportantMessage(ChrPtr(CmdBuf) + 4, StrLength(CmdBuf) - 4);
1684                         FreeStrBuf(&CmdBuf);
1685                         fixview();
1686                         readloop(readnew, eUseDefault);
1687                         return;
1688                 }
1689                 FreeStrBuf(&CmdBuf);
1690         }
1691         if (recipient_required)
1692                 PutBstr(HKEY("__RCPTREQUIRED"), NewStrBufPlain(HKEY("1")));
1693         if (recipient_required || subject_required)
1694                 PutBstr(HKEY("__SUBJREQUIRED"), NewStrBufPlain(HKEY("1")));
1695
1696         begin_burst();
1697         output_headers(1, 0, 0, 0, 1, 0);
1698         if (WCC->CurRoom.defview == VIEW_WIKIMD) 
1699                 DoTemplate(HKEY("edit_markdown_epic"), NULL, &NoCtx);
1700         else
1701                 DoTemplate(HKEY("edit_message"), NULL, &NoCtx);
1702         end_burst();
1703
1704         return;
1705 }
1706
1707 /*
1708  * delete a message
1709  */
1710 void delete_msg(void)
1711 {
1712         long msgid;
1713         StrBuf *Line;
1714         
1715         msgid = lbstr("msgid");
1716         Line = NewStrBuf();
1717         if ((WC->CurRoom.RAFlags & UA_ISTRASH) != 0) {  /* Delete from Trash is a real delete */
1718                 serv_printf("DELE %ld", msgid); 
1719         }
1720         else {                  /* Otherwise move it to Trash */
1721                 serv_printf("MOVE %ld|_TRASH_|0", msgid);
1722         }
1723
1724         StrBuf_ServGetln(Line);
1725         GetServerStatusMsg(Line, NULL, 1, 0);
1726
1727         fixview();
1728
1729         readloop(readnew, eUseDefault);
1730 }
1731
1732
1733 /*
1734  * move a message to another room
1735  */
1736 void move_msg(void)
1737 {
1738         long msgid;
1739
1740         msgid = lbstr("msgid");
1741
1742         if (havebstr("move_button")) {
1743                 StrBuf *Line;
1744                 serv_printf("MOVE %ld|%s", msgid, bstr("target_room"));
1745                 Line = NewStrBuf();
1746                 StrBuf_ServGetln(Line);
1747                 GetServerStatusMsg(Line, NULL, 1, 0);
1748                 FreeStrBuf(&Line);
1749         } else {
1750                 AppendImportantMessage(_("The message was not moved."), -1);
1751         }
1752
1753         fixview();
1754         readloop(readnew, eUseDefault);
1755 }
1756
1757
1758
1759 /*
1760  * Generic function to output an arbitrary MIME attachment from
1761  * message being composed
1762  *
1763  * partnum              The MIME part to be output
1764  * filename             Fake filename to give
1765  * force_download       Nonzero to force set the Content-Type: header to "application/octet-stream"
1766  */
1767 void postpart(StrBuf *partnum, StrBuf *filename, int force_download)
1768 {
1769         void *vPart;
1770         StrBuf *content_type;
1771         wc_mime_attachment *part;
1772         int i;
1773
1774         i = StrToi(partnum);
1775         if (GetHash(WC->attachments, IKEY(i), &vPart) &&
1776             (vPart != NULL)) {
1777                 part = (wc_mime_attachment*) vPart;
1778                 if (force_download) {
1779                         content_type = NewStrBufPlain(HKEY("application/octet-stream"));
1780                 }
1781                 else {
1782                         content_type = NewStrBufDup(part->ContentType);
1783                 }
1784                 StrBufAppendBuf(WC->WBuf, part->Data, 0);
1785                 http_transmit_thing(ChrPtr(content_type), 0);
1786         } else {
1787                 hprintf("HTTP/1.1 404 %s\n", ChrPtr(partnum));
1788                 output_headers(0, 0, 0, 0, 0, 0);
1789                 hprintf("Content-Type: text/plain\r\n");
1790                 begin_burst();
1791                 wc_printf(_("An error occurred while retrieving this part: %s/%s\n"), 
1792                         ChrPtr(partnum), ChrPtr(filename));
1793                 end_burst();
1794         }
1795         FreeStrBuf(&content_type);
1796 }
1797
1798
1799 /*
1800  * Generic function to output an arbitrary MIME part from an arbitrary
1801  * message number on the server.
1802  *
1803  * msgnum               Number of the item on the citadel server
1804  * partnum              The MIME part to be output
1805  * force_download       Nonzero to force set the Content-Type: header to "application/octet-stream"
1806  */
1807 void mimepart(int force_download)
1808 {
1809         int detect_mime = 0;
1810         long msgnum;
1811         long ErrorDetail;
1812         StrBuf *att;
1813         wcsession *WCC = WC;
1814         StrBuf *Buf;
1815         off_t bytes;
1816         StrBuf *ContentType = NewStrBufPlain(HKEY("application/octet-stream"));
1817         const char *CT;
1818
1819         att = Buf = NewStrBuf();
1820         msgnum = StrBufExtract_long(WCC->Hdr->HR.ReqLine, 0, '/');
1821         StrBufExtract_token(att, WCC->Hdr->HR.ReqLine, 1, '/');
1822
1823         serv_printf("OPNA %ld|%s", msgnum, ChrPtr(att));
1824         StrBuf_ServGetln(Buf);
1825         if (GetServerStatus(Buf, &ErrorDetail) == 2) {
1826                 StrBufCutLeft(Buf, 4);
1827                 bytes = StrBufExtract_long(Buf, 0, '|');
1828                 StrBufExtract_token(ContentType, Buf, 3, '|');
1829                 CheckGZipCompressionAllowed (SKEY(ContentType));
1830                 if (force_download)
1831                 {
1832                         FlushStrBuf(ContentType);
1833                         detect_mime = 0;
1834                 }
1835                 else
1836                 {
1837                         if (!strcasecmp(ChrPtr(ContentType), "application/octet-stream"))
1838                         {
1839                                 StrBufExtract_token(Buf, WCC->Hdr->HR.ReqLine, 2, '/');
1840                                 CT = GuessMimeByFilename(SKEY(Buf));
1841                                 StrBufPlain(ContentType, CT, -1);
1842                         }
1843                         if (!strcasecmp(ChrPtr(ContentType), "application/octet-stream"))
1844                         {
1845                                 detect_mime = 1;
1846                         }
1847                 }
1848                 serv_read_binary_to_http(ContentType, bytes, 0, detect_mime);
1849
1850                 serv_read_binary(WCC->WBuf, bytes, Buf);
1851                 serv_puts("CLOS");
1852                 StrBuf_ServGetln(Buf);
1853                 CT = ChrPtr(ContentType);
1854         } else {
1855                 StrBufCutLeft(Buf, 4);
1856                 switch (ErrorDetail) {
1857                 default:
1858                 case ERROR + MESSAGE_NOT_FOUND:
1859                         hprintf("HTTP/1.1 404 %s\n", ChrPtr(Buf));
1860                         break;
1861                 case ERROR + NOT_LOGGED_IN:
1862                         hprintf("HTTP/1.1 401 %s\n", ChrPtr(Buf));
1863                         break;
1864
1865                 case ERROR + HIGHER_ACCESS_REQUIRED:
1866                         hprintf("HTTP/1.1 403 %s\n", ChrPtr(Buf));
1867                         break;
1868                 case ERROR + INTERNAL_ERROR:
1869                 case ERROR + TOO_BIG:
1870                         hprintf("HTTP/1.1 500 %s\n", ChrPtr(Buf));
1871                         break;
1872                 }
1873
1874                 hprintf("Pragma: no-cache\r\n"
1875                         "Cache-Control: no-store\r\n"
1876                         "Expires: -1\r\n"
1877                 );
1878
1879                 hprintf("Content-Type: text/plain\r\n");
1880                 begin_burst();
1881                 wc_printf(_("An error occurred while retrieving this part: %s\n"), 
1882                         ChrPtr(Buf));
1883                 end_burst();
1884         }
1885         FreeStrBuf(&ContentType);
1886         FreeStrBuf(&Buf);
1887 }
1888
1889
1890 /*
1891  * Read any MIME part of a message, from the server, into memory.
1892  */
1893 StrBuf *load_mimepart(long msgnum, char *partnum)
1894 {
1895         off_t bytes;
1896         StrBuf *Buf;
1897         
1898         Buf = NewStrBuf();
1899         serv_printf("DLAT %ld|%s", msgnum, partnum);
1900         StrBuf_ServGetln(Buf);
1901         if (GetServerStatus(Buf, NULL) == 6) {
1902                 StrBufCutLeft(Buf, 4);
1903                 bytes = StrBufExtract_long(Buf, 0, '|');
1904                 FreeStrBuf(&Buf);
1905                 Buf = NewStrBuf();
1906                 StrBuf_ServGetBLOBBuffered(Buf, bytes);
1907                 return(Buf);
1908         }
1909         else {
1910                 FreeStrBuf(&Buf);
1911                 return(NULL);
1912         }
1913 }
1914
1915 /*
1916  * Read any MIME part of a message, from the server, into memory.
1917  */
1918 void MimeLoadData(wc_mime_attachment *Mime)
1919 {
1920         StrBuf *Buf;
1921         const char *Ptr;
1922         off_t bytes;
1923         /* TODO: is there a chance the content type is different from the one we know? */
1924
1925         serv_printf("DLAT %ld|%s", Mime->msgnum, ChrPtr(Mime->PartNum));
1926         Buf = NewStrBuf();
1927         StrBuf_ServGetln(Buf);
1928         if (GetServerStatus(Buf, NULL) == 6) {
1929                 Ptr = &(ChrPtr(Buf)[4]);
1930                 bytes = StrBufExtractNext_long(Buf, &Ptr, '|');
1931                 StrBufSkip_NTokenS(Buf, &Ptr, '|', 3);  /* filename, cbtype, mimetype */
1932                 if (Mime->Charset == NULL) Mime->Charset = NewStrBuf();
1933                 StrBufExtract_NextToken(Mime->Charset, Buf, &Ptr, '|');
1934                 
1935                 if (Mime->Data == NULL)
1936                         Mime->Data = NewStrBufPlain(NULL, bytes);
1937                 StrBuf_ServGetBLOBBuffered(Mime->Data, bytes);
1938         }
1939         else {
1940                 FlushStrBuf(Mime->Data);
1941                 /* TODO XImportant message */
1942         }
1943         FreeStrBuf(&Buf);
1944 }
1945
1946
1947 void view_mimepart(void) {
1948         mimepart(0);
1949 }
1950
1951 void download_mimepart(void) {
1952         mimepart(1);
1953 }
1954
1955 void view_postpart(void) {
1956         StrBuf *filename = NewStrBuf();
1957         StrBuf *partnum = NewStrBuf();
1958
1959         StrBufExtract_token(partnum, WC->Hdr->HR.ReqLine, 0, '/');
1960         StrBufExtract_token(filename, WC->Hdr->HR.ReqLine, 1, '/');
1961
1962         postpart(partnum, filename, 0);
1963
1964         FreeStrBuf(&filename);
1965         FreeStrBuf(&partnum);
1966 }
1967
1968 void download_postpart(void) {
1969         StrBuf *filename = NewStrBuf();
1970         StrBuf *partnum = NewStrBuf();
1971
1972         StrBufExtract_token(partnum, WC->Hdr->HR.ReqLine, 0, '/');
1973         StrBufExtract_token(filename, WC->Hdr->HR.ReqLine, 1, '/');
1974
1975         postpart(partnum, filename, 1);
1976
1977         FreeStrBuf(&filename);
1978         FreeStrBuf(&partnum);
1979 }
1980
1981
1982
1983 void show_num_attachments(void) {
1984         wc_printf("%d", GetCount(WC->attachments));
1985 }
1986
1987
1988 void h_readnew(void) { readloop(readnew, eUseDefault);}
1989 void h_readold(void) { readloop(readold, eUseDefault);}
1990 void h_readfwd(void) { readloop(readfwd, eUseDefault);}
1991 void h_headers(void) { readloop(headers, eUseDefault);}
1992 void h_do_search(void) { readloop(do_search, eUseDefault);}
1993 void h_readgt(void) { readloop(readgt, eUseDefault);}
1994 void h_readlt(void) { readloop(readlt, eUseDefault);}
1995
1996
1997
1998 /* Output message list in JSON format */
1999 void jsonMessageList(void) {
2000         StrBuf *View = NewStrBuf();
2001         const StrBuf *room = sbstr("room");
2002         long oper = (havebstr("query")) ? do_search : readnew;
2003         StrBufPrintf(View, "%d", VIEW_JSON_LIST);
2004         putbstr("view", View);; 
2005         gotoroom(room);
2006         readloop(oper, eUseDefault);
2007 }
2008
2009 void RegisterReadLoopHandlerset(
2010         int RoomType,
2011         GetParamsGetServerCall_func GetParamsGetServerCall,
2012         PrintViewHeader_func PrintPageHeader,
2013         PrintViewHeader_func PrintViewHeader,
2014         load_msg_ptrs_detailheaders LH,
2015         LoadMsgFromServer_func LoadMsgFromServer,
2016         RenderView_or_Tail_func RenderView_or_Tail,
2017         View_Cleanup_func ViewCleanup
2018         )
2019 {
2020         RoomRenderer *Handler;
2021
2022         Handler = (RoomRenderer*) malloc(sizeof(RoomRenderer));
2023
2024         Handler->RoomType = RoomType;
2025         Handler->GetParamsGetServerCall = GetParamsGetServerCall;
2026         Handler->PrintPageHeader = PrintPageHeader;
2027         Handler->PrintViewHeader = PrintViewHeader;
2028         Handler->LoadMsgFromServer = LoadMsgFromServer;
2029         Handler->RenderView_or_Tail = RenderView_or_Tail;
2030         Handler->ViewCleanup = ViewCleanup;
2031         Handler->LHParse = LH;
2032
2033         Put(ReadLoopHandler, IKEY(RoomType), Handler, NULL);
2034 }
2035
2036 void 
2037 InitModule_MSG
2038 (void)
2039 {
2040         RegisterPreference("use_sig",
2041                            _("Attach signature to email messages?"), 
2042                            PRF_YESNO, 
2043                            NULL);
2044         RegisterPreference("signature", _("Use this signature:"), PRF_QP_STRING, NULL);
2045         RegisterPreference("default_header_charset", 
2046                            _("Default character set for email headers:"), 
2047                            PRF_STRING, 
2048                            NULL);
2049         RegisterPreference("defaultfrom", _("Preferred email address"), PRF_STRING, NULL);
2050         RegisterPreference("defaultname", 
2051                            _("Preferred display name for email messages"), 
2052                            PRF_STRING, 
2053                            NULL);
2054         RegisterPreference("defaulthandle", 
2055                            _("Preferred display name for bulletin board posts"), 
2056                            PRF_STRING, 
2057                            NULL);
2058         RegisterPreference("mailbox",_("Mailbox view mode"), PRF_STRING, NULL);
2059
2060         WebcitAddUrlHandler(HKEY("readnew"), "", 0, h_readnew, ANONYMOUS|NEED_URL);
2061         WebcitAddUrlHandler(HKEY("readold"), "", 0, h_readold, ANONYMOUS|NEED_URL);
2062         WebcitAddUrlHandler(HKEY("readfwd"), "", 0, h_readfwd, ANONYMOUS|NEED_URL);
2063         WebcitAddUrlHandler(HKEY("headers"), "", 0, h_headers, NEED_URL);
2064         WebcitAddUrlHandler(HKEY("readgt"), "", 0, h_readgt, ANONYMOUS|NEED_URL);
2065         WebcitAddUrlHandler(HKEY("readlt"), "", 0, h_readlt, ANONYMOUS|NEED_URL);
2066         WebcitAddUrlHandler(HKEY("do_search"), "", 0, h_do_search, 0);
2067         WebcitAddUrlHandler(HKEY("display_enter"), "", 0, display_enter, 0);
2068         WebcitAddUrlHandler(HKEY("post"), "", 0, post_message, PROHIBIT_STARTPAGE);
2069         WebcitAddUrlHandler(HKEY("move_msg"), "", 0, move_msg, PROHIBIT_STARTPAGE);
2070         WebcitAddUrlHandler(HKEY("delete_msg"), "", 0, delete_msg, PROHIBIT_STARTPAGE);
2071         WebcitAddUrlHandler(HKEY("msg"), "", 0, embed_message, NEED_URL);
2072         WebcitAddUrlHandler(HKEY("message"), "", 0, handle_one_message, NEED_URL|XHTTP_COMMANDS|COOKIEUNNEEDED|FORCE_SESSIONCLOSE);
2073         WebcitAddUrlHandler(HKEY("printmsg"), "", 0, print_message, NEED_URL);
2074         WebcitAddUrlHandler(HKEY("msgheaders"), "", 0, display_headers, NEED_URL);
2075
2076         WebcitAddUrlHandler(HKEY("mimepart"), "", 0, view_mimepart, NEED_URL);
2077         WebcitAddUrlHandler(HKEY("mimepart_download"), "", 0, download_mimepart, NEED_URL);
2078         WebcitAddUrlHandler(HKEY("postpart"), "", 0, view_postpart, NEED_URL|PROHIBIT_STARTPAGE);
2079         WebcitAddUrlHandler(HKEY("postpart_download"), "", 0, download_postpart, NEED_URL|PROHIBIT_STARTPAGE);
2080         WebcitAddUrlHandler(HKEY("upload_attachment"), "", 0, upload_attachment, AJAX);
2081         WebcitAddUrlHandler(HKEY("remove_attachment"), "", 0, remove_attachment, AJAX);
2082         WebcitAddUrlHandler(HKEY("show_num_attachments"), "", 0, show_num_attachments, AJAX);
2083
2084         /* json */
2085         WebcitAddUrlHandler(HKEY("roommsgs"), "", 0, jsonMessageList,0);
2086
2087         l_subj = FourHash("subj", 4);
2088         l_wefw = FourHash("wefw", 4);
2089         l_msgn = FourHash("msgn", 4);
2090         l_from = FourHash("from", 4);
2091         l_rcpt = FourHash("rcpt", 4);
2092         l_cccc = FourHash("cccc", 4);
2093         l_replyto = FourHash("rep2", 4);
2094         l_node = FourHash("node", 4);
2095         l_rfca = FourHash("rfca", 4);
2096         l_nvto = FourHash("nvto", 4);
2097
2098         return ;
2099 }
2100
2101 void
2102 SessionDetachModule_MSG
2103 (wcsession *sess)
2104 {
2105         DeleteHash(&sess->summ);
2106 }