html render replaced by json render in the C server. next needs to be the json-to...
[citadel.git] / webcit-ng / forum_view.c
1 /*
2  * Forum view (threaded/flat)
3  *
4  * Copyright (c) 1996-2018 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include "webcit.h"
16
17 struct mthread {
18         long msgnum;
19         time_t datetime;
20         int threadhash;
21         int refhashes[10];
22         char from[64];
23         int parent;
24 };
25
26
27 // Renderer for one message in the threaded view
28 // (This will probably work for the flat view too.)
29 //
30 void forum_render_one_message(struct ctdlsession *c, StrBuf *sj, long msgnum)
31 {
32         StrBuf *raw_msg = NULL;
33         StrBuf *sanitized_msg = NULL;
34         char buf[1024];
35         char content_transfer_encoding[1024] = { 0 };
36         char content_type[1024] = { 0 };
37         char author[128] = { 0 };
38         char datetime[128] = { 0 } ;
39
40         ctdl_printf(c, "MSG4 %ld", msgnum);
41         ctdl_readline(c, buf, sizeof(buf));
42         if (buf[0] != '1') {
43                 StrBufAppendPrintf(sj, "<div>ERROR CONDITION FIXME WRITE A BOX</div>");
44                 return;
45         }
46
47         while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000")) ) {
48                 // citadel header parsing here
49                 if (!strncasecmp(buf, "from=", 5)) {
50                         safestrncpy(author, &buf[5], sizeof author);
51                 }
52                 if (!strncasecmp(buf, "time=", 5)) {
53                         time_t tt;
54                         struct tm tm;
55                         tt = atol(&buf[5]);
56                         localtime_r(&tt, &tm);
57                         strftime(datetime, sizeof datetime, "%c", &tm);
58                 }
59         }
60
61         if (!strcmp(buf, "text")) {
62                 while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000")) ) {
63                         // rfc822 header parsing here
64                         if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
65                                 strcpy(content_transfer_encoding, &buf[26]);
66                                 striplt(content_transfer_encoding);
67                         }
68                         if (!strncasecmp(buf, "Content-type:", 13)) {
69                                 strcpy(content_type, &buf[13]);
70                                 striplt(content_type);
71                         }
72                 }
73                 raw_msg = ctdl_readtextmsg(c);
74         }
75         else {
76                 raw_msg = NULL;
77         }
78
79         // begin output
80
81         StrBufAppendPrintf(sj, "<div>");                                                // begin message wrapper
82         StrBufAppendPrintf(sj, "<div style=\"float:left;padding-right:2px\">");         // begin avatar FIXME move the style to a stylesheet
83         StrBufAppendPrintf(sj, "<i class=\"fa fa-user-circle fa-2x\"></i> ");           // FIXME temporary avatar
84         StrBufAppendPrintf(sj, "</div>");                                               // end avatar
85         StrBufAppendPrintf(sj, "<div>");                                                // begin content
86         StrBufAppendPrintf(sj, "<div>");                                                // begin header
87         StrBufAppendPrintf(sj, "<span class=\"ctdl-username\"><a href=\"#\">%s</a></span> ", author);   // FIXME link to user profile or whatever
88         StrBufAppendPrintf(sj, "<span class=\"ctdl-msgdate\">%s</span> ", datetime);
89         StrBufAppendPrintf(sj, "</div>");                                               // end header
90         StrBufAppendPrintf(sj, "<div>");                                                // begin body
91
92         if (raw_msg) {
93
94                 // These are the encodings we know how to handle.  Decode in-place.
95
96                 if (!strcasecmp(content_transfer_encoding, "base64")) {
97                         StrBufDecodeBase64(raw_msg);
98                 }
99                 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
100                         StrBufDecodeQP(raw_msg);
101                 }
102
103                 // At this point, raw_msg contains the decoded message.
104                 // Now run through the renderers we have available.
105
106                 if (!strncasecmp(content_type, "text/html", 9)) {
107                         sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
108                 }
109                 else if (!strncasecmp(content_type, "text/plain", 10)) {
110                         sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
111                 }
112                 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
113                         sanitized_msg = variformat2html(raw_msg);
114                 }
115                 else {
116                         sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
117                 }
118                 FreeStrBuf(&raw_msg);
119
120                 // If sanitized_msg is not NULL, we have rendered the message and can output it.
121
122                 if (sanitized_msg) {
123                         StrBufAppendBuf(sj, sanitized_msg, 0);
124                         FreeStrBuf(&sanitized_msg);
125                 }
126         }
127
128         StrBufAppendPrintf(sj, "</div>");                                               // end body
129         StrBufAppendPrintf(sj, "</div>");                                               // end content
130         StrBufAppendPrintf(sj, "</div>");                                               // end wrapper
131 }
132
133
134 // Commands we need to send to Citadel Server before we begin rendering forum view.
135 // These are common to flat and threaded views.
136 //
137 void setup_for_forum_view(struct ctdlsession *c)
138 {
139         char buf[1024];
140         ctdl_printf(c, "MSGP text/html|text/plain");            // Declare the MIME types we know how to render
141         ctdl_readline(c, buf, sizeof(buf));                     // Ignore the response
142         ctdl_printf(c, "MSGP dont_decode");                     // Tell the server we will decode base64/etc client-side
143         ctdl_readline(c, buf, sizeof(buf));                     // Ignore the response
144 }
145
146
147 #if 0
148
149 // This code implements the thread display code.  The thread sorting algorithm is working nicely but we're trying
150 // not to do rendering in the C server of webcit.  Maybe move it into the server as "MSGS threaded" or something like that?
151
152 // Threaded view (recursive section)
153 //
154 void thread_o_print(struct ctdlsession *c, StrBuf *sj, struct mthread *m, int num_msgs, int where_parent_is, int nesting_level)
155 {
156         int i = 0;
157         int j = 0;
158         int num_printed = 0;
159
160         for (i=0; i<num_msgs; ++i) {
161                 if (m[i].parent == where_parent_is) {
162
163                         if (++num_printed == 1) {
164                                 StrBufAppendPrintf(sj, "<ul style=\"list-style-type: none;\">");
165                         }
166
167                         StrBufAppendPrintf(sj, "<li class=\"post\" id=\"post-%ld\">",  m[i].msgnum);
168                         forum_render_one_message(c, sj, m[i].msgnum);
169                         StrBufAppendPrintf(sj, "</li>\r\n");
170                         if (i != 0) {
171                                 thread_o_print(c, sj, m, num_msgs, i, nesting_level+1);
172                         }
173                 }
174         }
175
176         if (num_printed > 0) {
177                 StrBufAppendPrintf(sj, "</ul>");
178         }
179 }
180
181
182 // Threaded view (entry point)
183 //
184 void threaded_view(struct http_transaction *h, struct ctdlsession *c, char *which)
185 {
186         int num_msgs = 0;
187         int num_alloc = 0;
188         struct mthread *m;
189         char buf[1024];
190         char refs[1024];
191         int i, j, k;
192
193         ctdl_printf(c, "MSGS ALL|||9");                 // 9 == headers + thread references
194         ctdl_readline(c, buf, sizeof(buf));
195         if (buf[0] != '1') {
196                 do_404(h);
197                 return;
198         }
199
200         StrBuf *sj = NewStrBuf();
201         StrBufAppendPrintf(sj, "<html><body>\r\n");
202
203         while (ctdl_readline(c, buf, sizeof buf), strcmp(buf,"000")) {
204
205                 ++num_msgs;
206                 if (num_msgs > num_alloc) {
207                         if (num_alloc == 0) {
208                                 num_alloc = 100;
209                                 m = malloc(num_alloc * sizeof(struct mthread));
210                         }
211                         else {
212                                 num_alloc *= 2;
213                                 m = realloc(m, (num_alloc * sizeof(struct mthread)));
214                         }
215                 }
216
217                 memset(&m[num_msgs-1], 0, sizeof(struct mthread));
218                 m[num_msgs-1].msgnum = extract_long(buf, 0);
219                 m[num_msgs-1].datetime = extract_long(buf, 1);
220                 extract_token(m[num_msgs-1].from, buf, 2, '|', sizeof m[num_msgs-1].from);
221                 m[num_msgs-1].threadhash = extract_int(buf, 6);
222                 extract_token(refs, buf, 7, '|', sizeof refs);
223
224                 char *t;
225                 char *r = refs;
226                 i = 0;
227                 while ((t = strtok_r(r, ",", &r))) {
228                         if (i == 0) {
229                                 m[num_msgs-1].refhashes[0] = atoi(t);           // always keep the first one
230                         }
231                         else {
232                                 memcpy(&m[num_msgs-1].refhashes[1], &m[num_msgs-1].refhashes[2], sizeof(int)*8 );       // shift the rest
233                                 m[num_msgs-1].refhashes[9] = atoi(t);
234                         }
235                         ++i;
236                 }
237
238         }
239
240         // Sort by thread.  I did read jwz's sorting algorithm and it looks pretty good, but jwz is a self-righteous asshole so we do it our way.
241         for (i=0; i<num_msgs; ++i) {
242                 for (j=9; (j>=0)&&(m[i].parent==0); --j) {
243                         for (k=0; (k<num_msgs)&&(m[i].parent==0); ++k) {
244                                 if (m[i].refhashes[j] == m[k].threadhash) {
245                                         m[i].parent = k;
246                                 }
247                         }
248                 }
249         }
250
251         // Now render it
252         setup_for_forum_view(c);
253         thread_o_print(c, sj, m, num_msgs, 0, 0);               // Render threads recursively and recursively
254
255         // Garbage collection is for people who aren't smart enough to manage their own memory.
256         if (num_msgs > 0) {
257                 free(m);
258         }
259
260         StrBufAppendPrintf(sj, "</body></html>\r\n");
261
262         add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
263         h->response_code = 200;
264         h->response_string = strdup("OK");
265         h->response_body_length = StrLength(sj);
266         h->response_body = SmashStrBuf(&sj);
267         return;
268 }
269
270
271 // flat view (entry point)
272 //
273 void flat_view(struct http_transaction *h, struct ctdlsession *c, char *which)
274 {
275         StrBuf *sj = NewStrBuf();
276         StrBufAppendPrintf(sj, "<html><body>\r\n");
277
278         setup_for_forum_view(c);
279         long *msglist = get_msglist(c, "ALL");
280         if (msglist) {
281                 int i;
282                 for (i=0; (msglist[i] > 0); ++i) {
283                         forum_render_one_message(c, sj, msglist[i]);
284                 }
285                 free(msglist);
286         }
287
288         StrBufAppendPrintf(sj, "</body></html>\r\n");
289
290         add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
291         h->response_code = 200;
292         h->response_string = strdup("OK");
293         h->response_body_length = StrLength(sj);
294         h->response_body = SmashStrBuf(&sj);
295         return;
296 }
297
298
299 // render one message (entire transaction)      FIXME EXTERMINATE
300 //
301 void html_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
302 {
303         StrBuf *sj = NewStrBuf();
304         StrBufAppendPrintf(sj, "<html><body>\r\n");
305         setup_for_forum_view(c);                // FIXME way too inefficient to do this for every message !!!!!!!!!!!!!
306         forum_render_one_message(c, sj, msgnum);
307         StrBufAppendPrintf(sj, "</body></html>\r\n");
308         add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
309         h->response_code = 200;
310         h->response_string = strdup("OK");
311         h->response_body_length = StrLength(sj);
312         h->response_body = SmashStrBuf(&sj);
313         return;
314 }
315
316 #endif
317
318 // Fetch a single message and return it in JSON format for client-side rendering
319 //
320 void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
321 {
322         StrBuf *raw_msg = NULL;
323         StrBuf *sanitized_msg = NULL;
324         char buf[1024];
325         char content_transfer_encoding[1024] = { 0 };
326         char content_type[1024] = { 0 };
327         char author[128] = { 0 };
328         char datetime[128] = { 0 } ;
329
330         setup_for_forum_view(c);
331
332         ctdl_printf(c, "MSG4 %ld", msgnum);
333         ctdl_readline(c, buf, sizeof(buf));
334         if (buf[0] != '1') {
335                 do_404(h);
336                 return;
337         }
338
339         JsonValue *j = NewJsonObject(HKEY("message"));
340
341         while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000")) ) {
342                 // citadel header parsing here
343                 if (!strncasecmp(buf, "from=", 5)) {
344                         JsonObjectAppend(j, NewJsonPlainString( HKEY("from"), &buf[5], -1));
345                 }
346                 if (!strncasecmp(buf, "rfca=", 5)) {
347                         JsonObjectAppend(j, NewJsonPlainString( HKEY("from"), &buf[5], -1));
348                 }
349                 if (!strncasecmp(buf, "time=", 5)) {
350                         time_t tt;
351                         struct tm tm;
352                         tt = atol(&buf[5]);
353                         localtime_r(&tt, &tm);
354                         strftime(datetime, sizeof datetime, "%c", &tm);
355                         JsonObjectAppend(j, NewJsonPlainString( HKEY("time"), datetime, -1));
356                 }
357         }
358
359         if (!strcmp(buf, "text")) {
360                 while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000")) ) {
361                         // rfc822 header parsing here
362                         if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
363                                 strcpy(content_transfer_encoding, &buf[26]);
364                                 striplt(content_transfer_encoding);
365                         }
366                         if (!strncasecmp(buf, "Content-type:", 13)) {
367                                 strcpy(content_type, &buf[13]);
368                                 striplt(content_type);
369                         }
370                 }
371                 raw_msg = ctdl_readtextmsg(c);
372         }
373         else {
374                 raw_msg = NULL;
375         }
376
377         if (raw_msg) {
378
379                 // These are the encodings we know how to handle.  Decode in-place.
380
381                 if (!strcasecmp(content_transfer_encoding, "base64")) {
382                         StrBufDecodeBase64(raw_msg);
383                 }
384                 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
385                         StrBufDecodeQP(raw_msg);
386                 }
387
388                 // At this point, raw_msg contains the decoded message.
389                 // Now run through the renderers we have available.
390
391                 if (!strncasecmp(content_type, "text/html", 9)) {
392                         sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
393                 }
394                 else if (!strncasecmp(content_type, "text/plain", 10)) {
395                         sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
396                 }
397                 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
398                         sanitized_msg = variformat2html(raw_msg);
399                 }
400                 else {
401                         sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
402                 }
403                 FreeStrBuf(&raw_msg);
404
405                 // If sanitized_msg is not NULL, we have rendered the message and can output it.
406
407                 if (sanitized_msg) {
408                         JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
409                 }
410         }
411
412         StrBuf *sj = NewStrBuf();
413         SerializeJson(sj, j, 1);                        // '1' == free the source object
414
415         add_response_header(h, strdup("Content-type"), strdup("application/json"));
416         h->response_code = 200;
417         h->response_string = strdup("OK");
418         h->response_body_length = StrLength(sj);
419         h->response_body = SmashStrBuf(&sj);
420 }