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