89303256aa0a83bbacd8c1925099bd4a0bf83abb
[citadel.git] / webcit-ng / server / 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-2023 by the citadel.org team
5 //
6 // This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
7
8 #include "webcit.h"
9
10 // Commands we need to send to Citadel Server before we begin rendering forum view.
11 // These are common to flat and threaded views.
12 void setup_for_forum_view(struct ctdlsession *c) {
13         char buf[1024];
14         ctdl_printf(c, "MSGP text/html|text/plain");    // Declare the MIME types we know how to render
15         ctdl_readline(c, buf, sizeof(buf));             // Ignore the response
16         ctdl_printf(c, "MSGP dont_decode");             // Tell the server we will decode base64/etc client-side
17         ctdl_readline(c, buf, sizeof(buf));             // Ignore the response
18 }
19
20
21 // Convenience function for json_render_one_message()
22 // Converts a string of comma-separated recipients into a JSON Array
23 JsonValue *json_tokenize_recipients(const char *Key, long keylen, char *recp) {
24         char tokbuf[1024];
25
26         JsonValue *j = NewJsonArray(Key, keylen);
27         int num_recp = num_tokens(recp, ',');
28         for (int i=0; i<num_recp; ++i) {
29                 extract_token(tokbuf, recp, i, ',', sizeof(tokbuf));
30                 string_trim(tokbuf);
31                 JsonArrayAppend(j, NewJsonPlainString(HKEY("r"), tokbuf, strlen(tokbuf)));
32         }
33         return(j);
34 }
35
36
37 // Fetch a single message and return it in JSON format for client-side rendering
38 void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum) {
39         StrBuf *raw_msg = NULL;
40         StrBuf *sanitized_msg = NULL;
41         char buf[1024];
42         char content_transfer_encoding[1024] = { 0 };
43         char content_type[1024] = { 0 };
44         char datetime[128] = { 0 };
45         int message_originated_locally = 0;
46
47         setup_for_forum_view(c);
48
49         ctdl_printf(c, "MSG4 %ld", msgnum);
50         ctdl_readline(c, buf, sizeof(buf));
51         if (buf[0] != '1') {
52                 do_404(h);
53                 return;
54         }
55
56         JsonValue *attachments = NULL;
57
58         JsonValue *j = NewJsonObject(HKEY(""));
59         JsonObjectAppend(j, NewJsonNumber(HKEY("msgnum"), msgnum));
60
61         while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000"))) {
62                 utf8ify_rfc822_string(&buf[5]);
63
64                 // citadel header parsing here
65                 if (!strncasecmp(buf, "from=", 5)) {
66                         JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), &buf[5], -1));
67                 }
68                 else if (!strncasecmp(buf, "rfca=", 5)) {
69                         JsonObjectAppend(j, NewJsonPlainString(HKEY("rfca"), &buf[5], -1));
70                 }
71                 else if (!strncasecmp(buf, "time=", 5)) {
72                         JsonObjectAppend(j, NewJsonNumber(HKEY("time"), atol(&buf[5])));
73                 }
74                 else if (!strncasecmp(buf, "locl=", 5)) {
75                         message_originated_locally = 1;
76                 }
77                 else if (!strncasecmp(buf, "subj=", 5)) {
78                         JsonObjectAppend(j, NewJsonPlainString(HKEY("subj"), &buf[5], -1));
79                 }
80                 else if (!strncasecmp(buf, "msgn=", 5)) {
81                         JsonObjectAppend(j, NewJsonPlainString(HKEY("msgn"), &buf[5], -1));
82                 }
83                 else if (!strncasecmp(buf, "wefw=", 5)) {
84                         JsonObjectAppend(j, NewJsonPlainString(HKEY("wefw"), &buf[5], -1));
85                 }
86                 else if (!strncasecmp(buf, "rcpt=", 5)) {
87                         JsonObjectAppend(j, json_tokenize_recipients(HKEY("rcpt"), &buf[5]));
88                 }
89                 else if (!strncasecmp(buf, "cccc=", 5)) {
90                         JsonObjectAppend(j, json_tokenize_recipients(HKEY("cccc"), &buf[5]));
91                 }
92                 else if (!strncasecmp(buf, "part=", 5)) {
93                         if (attachments == NULL) {
94                                 attachments = NewJsonArray(HKEY("part"));
95                         }
96                         JsonValue *part = NewJsonObject(HKEY(""));
97                         char tokbuf[1024];
98                         extract_token(tokbuf, &buf[5], 0, '|', sizeof tokbuf);
99                         JsonObjectAppend(part, NewJsonPlainString(HKEY("name"), tokbuf, -1));
100                         extract_token(tokbuf, &buf[5], 1, '|', sizeof tokbuf);
101                         JsonObjectAppend(part, NewJsonPlainString(HKEY("filename"), tokbuf, -1));
102                         extract_token(tokbuf, &buf[5], 2, '|', sizeof tokbuf);
103                         JsonObjectAppend(part, NewJsonPlainString(HKEY("partnum"), tokbuf, -1));
104                         extract_token(tokbuf, &buf[5], 3, '|', sizeof tokbuf);
105                         JsonObjectAppend(part, NewJsonPlainString(HKEY("disp"), tokbuf, -1));
106                         extract_token(tokbuf, &buf[5], 4, '|', sizeof tokbuf);
107                         JsonObjectAppend(part, NewJsonPlainString(HKEY("type"), tokbuf, -1));
108                         JsonObjectAppend(part, NewJsonNumber(HKEY("len"), extract_long(&buf[5], 5)));
109                         extract_token(tokbuf, &buf[5], 6, '|', sizeof tokbuf);
110                         JsonObjectAppend(part, NewJsonPlainString(HKEY("id"), tokbuf, -1));
111                         extract_token(tokbuf, &buf[5], 7, '|', sizeof tokbuf);
112                         JsonObjectAppend(part, NewJsonPlainString(HKEY("charset"), tokbuf, -1));
113                         JsonArrayAppend(attachments, part);
114                 }
115                 else {
116                         // unhandled header
117                 }
118         }
119         JsonObjectAppend(j, NewJsonNumber(HKEY("locl"), message_originated_locally));
120         if (attachments != NULL) {
121                 JsonObjectAppend(j, attachments);
122         }
123
124         if (!strcmp(buf, "text")) {
125                 while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000"))) {
126                         // rfc822 header parsing here
127                         if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
128                                 strcpy(content_transfer_encoding, &buf[26]);
129                                 string_trim(content_transfer_encoding);
130                         }
131                         if (!strncasecmp(buf, "Content-type:", 13)) {
132                                 strcpy(content_type, &buf[13]);
133                                 string_trim(content_type);
134                         }
135                 }
136                 if (!strcmp(buf, "000")) {              // if we have an empty message, don't try to read further
137                         raw_msg = NULL;
138                 }
139                 else {
140                         raw_msg = ctdl_readtextmsg(c);
141                 }
142         }
143         else {
144                 raw_msg = NULL;
145         }
146
147         if (raw_msg) {
148                 // These are the encodings we know how to handle.  Decode in-place.
149
150                 if (!strcasecmp(content_transfer_encoding, "base64")) {
151                         StrBufDecodeBase64(raw_msg);
152                 }
153                 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
154                         StrBufDecodeQP(raw_msg);
155                 }
156
157                 // At this point, raw_msg contains the decoded message.
158                 // Now run through the renderers we have available.
159
160                 if (!strncasecmp(content_type, "text/html", 9)) {
161                         sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
162                 }
163                 else if (!strncasecmp(content_type, "text/plain", 10)) {
164                         sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
165                 }
166                 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
167                         sanitized_msg = variformat2html(raw_msg);
168                 }
169                 else {
170                         sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
171                         syslog(LOG_WARNING, "forum_view: no renderer for content type %s", content_type);
172                 }
173                 FreeStrBuf(&raw_msg);
174
175                 // If sanitized_msg is not NULL, we have rendered the message and can output it.
176                 if (sanitized_msg) {
177                         JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
178                 }
179                 else {
180                         syslog(LOG_WARNING, "forum_view: message %ld of content type %s converted to NULL", msgnum, content_type);
181                 }
182         }
183
184         StrBuf *sj = NewStrBuf();
185         SerializeJson(sj, j, 1);        // '1' == free the source object
186
187         add_response_header(h, strdup("Content-type"), strdup("application/json"));
188         h->response_code = 200;
189         h->response_string = strdup("OK");
190         h->response_body_length = StrLength(sj);
191         h->response_body = SmashStrBuf(&sj);
192 }