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