striplt() is now string_trim()
[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-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 // 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 *j = NewJsonObject(HKEY("message"));
58         JsonObjectAppend(j, NewJsonNumber(HKEY("msgnum"), msgnum));
59
60         while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000"))) {
61                 utf8ify_rfc822_string(&buf[5]);
62
63                 // citadel header parsing here
64                 if (!strncasecmp(buf, "from=", 5)) {
65                         JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), &buf[5], -1));
66                 }
67                 else if (!strncasecmp(buf, "rfca=", 5)) {
68                         JsonObjectAppend(j, NewJsonPlainString(HKEY("rfca"), &buf[5], -1));
69                 }
70                 else if (!strncasecmp(buf, "time=", 5)) {
71                         JsonObjectAppend(j, NewJsonNumber(HKEY("time"), atol(&buf[5])));
72                 }
73                 else if (!strncasecmp(buf, "locl=", 5)) {
74                         message_originated_locally = 1;
75                 }
76                 else if (!strncasecmp(buf, "subj=", 5)) {
77                         JsonObjectAppend(j, NewJsonPlainString(HKEY("subj"), &buf[5], -1));
78                 }
79                 else if (!strncasecmp(buf, "msgn=", 5)) {
80                         JsonObjectAppend(j, NewJsonPlainString(HKEY("msgn"), &buf[5], -1));
81                 }
82                 else if (!strncasecmp(buf, "wefw=", 5)) {
83                         JsonObjectAppend(j, NewJsonPlainString(HKEY("wefw"), &buf[5], -1));
84                 }
85                 else if (!strncasecmp(buf, "rcpt=", 5)) {
86                         JsonObjectAppend(j, json_tokenize_recipients(HKEY("rcpt"), &buf[5]));
87                 }
88                 else if (!strncasecmp(buf, "cccc=", 5)) {
89                         JsonObjectAppend(j, json_tokenize_recipients(HKEY("cccc"), &buf[5]));
90                 }
91         }
92         JsonObjectAppend(j, NewJsonNumber(HKEY("locl"), message_originated_locally));
93
94         if (!strcmp(buf, "text")) {
95                 while ((ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000"))) {
96                         // rfc822 header parsing here
97                         if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
98                                 strcpy(content_transfer_encoding, &buf[26]);
99                                 string_trim(content_transfer_encoding);
100                         }
101                         if (!strncasecmp(buf, "Content-type:", 13)) {
102                                 strcpy(content_type, &buf[13]);
103                                 string_trim(content_type);
104                         }
105                 }
106                 if (!strcmp(buf, "000")) {              // if we have an empty message, don't try to read further
107                         raw_msg = NULL;
108                 }
109                 else {
110                         raw_msg = ctdl_readtextmsg(c);
111                 }
112         }
113         else {
114                 raw_msg = NULL;
115         }
116
117         if (raw_msg) {
118                 // These are the encodings we know how to handle.  Decode in-place.
119
120                 if (!strcasecmp(content_transfer_encoding, "base64")) {
121                         StrBufDecodeBase64(raw_msg);
122                 }
123                 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
124                         StrBufDecodeQP(raw_msg);
125                 }
126
127                 // At this point, raw_msg contains the decoded message.
128                 // Now run through the renderers we have available.
129
130                 if (!strncasecmp(content_type, "text/html", 9)) {
131                         sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
132                 }
133                 else if (!strncasecmp(content_type, "text/plain", 10)) {
134                         sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
135                 }
136                 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
137                         sanitized_msg = variformat2html(raw_msg);
138                 }
139                 else {
140                         sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
141                         syslog(LOG_WARNING, "forum_view: no renderer for content type %s", content_type);
142                 }
143                 FreeStrBuf(&raw_msg);
144
145                 // If sanitized_msg is not NULL, we have rendered the message and can output it.
146                 if (sanitized_msg) {
147                         JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
148                 }
149                 else {
150                         syslog(LOG_WARNING, "forum_view: message %ld of content type %s converted to NULL", msgnum, content_type);
151                 }
152         }
153
154         StrBuf *sj = NewStrBuf();
155         SerializeJson(sj, j, 1);        // '1' == free the source object
156
157         add_response_header(h, strdup("Content-type"), strdup("application/json"));
158         h->response_code = 200;
159         h->response_string = strdup("OK");
160         h->response_body_length = StrLength(sj);
161         h->response_body = SmashStrBuf(&sj);
162 }