initial work on inline rendering setup
[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 // Threaded view (recursive section)
148 //
149 void thread_o_print(struct ctdlsession *c, StrBuf *sj, struct mthread *m, int num_msgs, int where_parent_is, int nesting_level)
150 {
151         int i = 0;
152         int j = 0;
153         int num_printed = 0;
154
155         for (i=0; i<num_msgs; ++i) {
156                 if (m[i].parent == where_parent_is) {
157
158                         if (++num_printed == 1) {
159                                 StrBufAppendPrintf(sj, "<ul style=\"list-style-type: none;\">");
160                         }
161
162                         StrBufAppendPrintf(sj, "<li class=\"post\" id=\"post-%ld\">",  m[i].msgnum);
163                         forum_render_one_message(c, sj, m[i].msgnum);
164                         StrBufAppendPrintf(sj, "</li>\r\n");
165                         if (i != 0) {
166                                 thread_o_print(c, sj, m, num_msgs, i, nesting_level+1);
167                         }
168                 }
169         }
170
171         if (num_printed > 0) {
172                 StrBufAppendPrintf(sj, "</ul>");
173         }
174 }
175
176 #if 0
177
178 // Threaded view (entry point)
179 //
180 void threaded_view(struct http_transaction *h, struct ctdlsession *c, char *which)
181 {
182         int num_msgs = 0;
183         int num_alloc = 0;
184         struct mthread *m;
185         char buf[1024];
186         char refs[1024];
187         int i, j, k;
188
189         ctdl_printf(c, "MSGS ALL|||9");                 // 9 == headers + thread references
190         ctdl_readline(c, buf, sizeof(buf));
191         if (buf[0] != '1') {
192                 do_404(h);
193                 return;
194         }
195
196         StrBuf *sj = NewStrBuf();
197         StrBufAppendPrintf(sj, "<html><body>\r\n");
198
199         while (ctdl_readline(c, buf, sizeof buf), strcmp(buf,"000")) {
200
201                 ++num_msgs;
202                 if (num_msgs > num_alloc) {
203                         if (num_alloc == 0) {
204                                 num_alloc = 100;
205                                 m = malloc(num_alloc * sizeof(struct mthread));
206                         }
207                         else {
208                                 num_alloc *= 2;
209                                 m = realloc(m, (num_alloc * sizeof(struct mthread)));
210                         }
211                 }
212
213                 memset(&m[num_msgs-1], 0, sizeof(struct mthread));
214                 m[num_msgs-1].msgnum = extract_long(buf, 0);
215                 m[num_msgs-1].datetime = extract_long(buf, 1);
216                 extract_token(m[num_msgs-1].from, buf, 2, '|', sizeof m[num_msgs-1].from);
217                 m[num_msgs-1].threadhash = extract_int(buf, 6);
218                 extract_token(refs, buf, 7, '|', sizeof refs);
219
220                 char *t;
221                 char *r = refs;
222                 i = 0;
223                 while ((t = strtok_r(r, ",", &r))) {
224                         if (i == 0) {
225                                 m[num_msgs-1].refhashes[0] = atoi(t);           // always keep the first one
226                         }
227                         else {
228                                 memcpy(&m[num_msgs-1].refhashes[1], &m[num_msgs-1].refhashes[2], sizeof(int)*8 );       // shift the rest
229                                 m[num_msgs-1].refhashes[9] = atoi(t);
230                         }
231                         ++i;
232                 }
233
234         }
235
236         // 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.
237         for (i=0; i<num_msgs; ++i) {
238                 for (j=9; (j>=0)&&(m[i].parent==0); --j) {
239                         for (k=0; (k<num_msgs)&&(m[i].parent==0); ++k) {
240                                 if (m[i].refhashes[j] == m[k].threadhash) {
241                                         m[i].parent = k;
242                                 }
243                         }
244                 }
245         }
246
247         // Now render it
248         setup_for_forum_view(c);
249         thread_o_print(c, sj, m, num_msgs, 0, 0);               // Render threads recursively and recursively
250
251         // Garbage collection is for people who aren't smart enough to manage their own memory.
252         if (num_msgs > 0) {
253                 free(m);
254         }
255
256         StrBufAppendPrintf(sj, "</body></html>\r\n");
257
258         add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
259         h->response_code = 200;
260         h->response_string = strdup("OK");
261         h->response_body_length = StrLength(sj);
262         h->response_body = SmashStrBuf(&sj);
263         return;
264 }
265
266
267 // flat view (entry point)
268 //
269 void flat_view(struct http_transaction *h, struct ctdlsession *c, char *which)
270 {
271         StrBuf *sj = NewStrBuf();
272         StrBufAppendPrintf(sj, "<html><body>\r\n");
273
274         setup_for_forum_view(c);
275         long *msglist = get_msglist(c, "ALL");
276         if (msglist) {
277                 int i;
278                 for (i=0; (msglist[i] > 0); ++i) {
279                         forum_render_one_message(c, sj, msglist[i]);
280                 }
281                 free(msglist);
282         }
283
284         StrBufAppendPrintf(sj, "</body></html>\r\n");
285
286         add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
287         h->response_code = 200;
288         h->response_string = strdup("OK");
289         h->response_body_length = StrLength(sj);
290         h->response_body = SmashStrBuf(&sj);
291         return;
292 }
293
294 #endif
295
296 // render one message (entire transaction)
297 //
298 void html_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
299 {
300
301         StrBuf *sj = NewStrBuf();
302         StrBufAppendPrintf(sj, "<html><body>\r\n");
303         setup_for_forum_view(c);                // FIXME way too inefficient to do this for every message !!!!!!!!!!!!!
304         forum_render_one_message(c, sj, msgnum);
305         StrBufAppendPrintf(sj, "</body></html>\r\n");
306         add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
307         h->response_code = 200;
308         h->response_string = strdup("OK");
309         h->response_body_length = StrLength(sj);
310         h->response_body = SmashStrBuf(&sj);
311         return;
312 }
313