Convert RFC2047-encoded strings to UTF8 before display
[citadel.git] / webcit-ng / forum_view.c
1 // The code in here feeds messages out as JSON to the client browser.  It is currently being used
2 // for the forum view, but as we implement other views we'll probably reuse a lot of what's here.
3 //
4 // Copyright (c) 1996-2022 by the citadel.org team
5 //
6 // This program is open source software.  Use, duplication, or
7 // disclosure are subject to the GNU General Public License v3.
8
9 #include "webcit.h"
10
11 // Commands we need to send to Citadel Server before we begin rendering forum view.
12 // These are common to flat and threaded views.
13 void setup_for_forum_view(struct ctdlsession *c) {
14         char buf[1024];
15         ctdl_printf(c, "MSGP text/html|text/plain");    // Declare the MIME types we know how to render
16         ctdl_readline(c, buf, sizeof(buf));             // Ignore the response
17         ctdl_printf(c, "MSGP dont_decode");             // Tell the server we will decode base64/etc client-side
18         ctdl_readline(c, buf, sizeof(buf));             // Ignore the response
19 }
20
21
22 // Fetch a single message and return it in JSON format for client-side rendering
23 void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum) {
24         StrBuf *raw_msg = NULL;
25         StrBuf *sanitized_msg = NULL;
26         char buf[1024];
27         char content_transfer_encoding[1024] = { 0 };
28         char content_type[1024] = { 0 };
29         char datetime[128] = { 0 };
30         char author[1024] = { 0 };
31         char emailaddr[1024] = { 0 };
32         int message_originated_locally = 0;
33
34         setup_for_forum_view(c);
35
36         ctdl_printf(c, "MSG4 %ld", msgnum);
37         ctdl_readline(c, buf, sizeof(buf));
38         if (buf[0] != '1') {
39                 do_404(h);
40                 return;
41         }
42
43         JsonValue *j = NewJsonObject(HKEY("message"));
44         JsonObjectAppend(j, NewJsonNumber(HKEY("msgnum"), msgnum));
45
46         while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000"))) {
47                 utf8ify_rfc822_string(&buf[5]);
48
49                 // citadel header parsing here
50                 if (!strncasecmp(buf, "from=", 5)) {
51                         safestrncpy(author, &buf[5], sizeof author);
52                 }
53                 else if (!strncasecmp(buf, "rfca=", 5)) {
54                         safestrncpy(emailaddr, &buf[5], sizeof emailaddr);
55                 }
56                 else if (!strncasecmp(buf, "time=", 5)) {
57                         JsonObjectAppend(j, NewJsonNumber(HKEY("time"), atol(&buf[5])));
58                 }
59                 else if (!strncasecmp(buf, "locl=", 5)) {
60                         message_originated_locally = 1;
61                 }
62                 else if (!strncasecmp(buf, "subj=", 5)) {
63                         JsonObjectAppend(j, NewJsonPlainString(HKEY("subj"), &buf[5], -1));
64                 }
65                 else if (!strncasecmp(buf, "msgn=", 5)) {
66                         JsonObjectAppend(j, NewJsonPlainString(HKEY("msgn"), &buf[5], -1));
67                 }
68                 else if (!strncasecmp(buf, "wefw=", 5)) {
69                         JsonObjectAppend(j, NewJsonPlainString(HKEY("wefw"), &buf[5], -1));
70                 }
71         }
72
73         if (message_originated_locally) {
74                 JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), author, -1));
75         }
76         else {
77                 JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), emailaddr, -1));           // FIXME do compound address string
78         }
79
80         if (!strcmp(buf, "text")) {
81                 while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000"))) {
82                         // rfc822 header parsing here
83                         if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
84                                 strcpy(content_transfer_encoding, &buf[26]);
85                                 striplt(content_transfer_encoding);
86                         }
87                         if (!strncasecmp(buf, "Content-type:", 13)) {
88                                 strcpy(content_type, &buf[13]);
89                                 striplt(content_type);
90                         }
91                 }
92                 if (!strcmp(buf, "000")) {              // if we have an empty message, don't try to read further
93                         raw_msg = NULL;
94                 }
95                 else {
96                         raw_msg = ctdl_readtextmsg(c);
97                 }
98         }
99         else {
100                 raw_msg = NULL;
101         }
102
103         if (raw_msg) {
104                 // These are the encodings we know how to handle.  Decode in-place.
105
106                 if (!strcasecmp(content_transfer_encoding, "base64")) {
107                         StrBufDecodeBase64(raw_msg);
108                 }
109                 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
110                         StrBufDecodeQP(raw_msg);
111                 }
112
113                 // At this point, raw_msg contains the decoded message.
114                 // Now run through the renderers we have available.
115
116                 if (!strncasecmp(content_type, "text/html", 9)) {
117                         sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
118                 }
119                 else if (!strncasecmp(content_type, "text/plain", 10)) {
120                         sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
121                 }
122                 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
123                         sanitized_msg = variformat2html(raw_msg);
124                 }
125                 else {
126                         sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
127                         syslog(LOG_WARNING, "forum_view: no renderer for content type %s", content_type);
128                 }
129                 FreeStrBuf(&raw_msg);
130
131                 // If sanitized_msg is not NULL, we have rendered the message and can output it.
132                 if (sanitized_msg) {
133                         JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
134                 }
135                 else {
136                         syslog(LOG_WARNING, "forum_view: message %ld of content type %s converted to NULL", msgnum, content_type);
137                 }
138         }
139
140         StrBuf *sj = NewStrBuf();
141         SerializeJson(sj, j, 1);        // '1' == free the source object
142
143         add_response_header(h, strdup("Content-type"), strdup("application/json"));
144         h->response_code = 200;
145         h->response_string = strdup("OK");
146         h->response_body_length = StrLength(sj);
147         h->response_body = SmashStrBuf(&sj);
148 }