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