2 * Forum view (threaded/flat)
4 * Copyright (c) 1996-2018 by the citadel.org team
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.
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.
27 // Commands we need to send to Citadel Server before we begin rendering forum view.
28 // These are common to flat and threaded views.
30 void setup_for_forum_view(struct ctdlsession *c)
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
41 // Renderer for one message in the threaded view
42 // (This will probably work for the flat view too.)
44 void forum_render_one_message(struct ctdlsession *c, StrBuf * sj, long msgnum)
46 StrBuf *raw_msg = NULL;
47 StrBuf *sanitized_msg = NULL;
49 char content_transfer_encoding[1024] = { 0 };
50 char content_type[1024] = { 0 };
51 char author[128] = { 0 };
52 char datetime[128] = { 0 };
54 ctdl_printf(c, "MSG4 %ld", msgnum);
55 ctdl_readline(c, buf, sizeof(buf));
57 StrBufAppendPrintf(sj, "<div>ERROR CONDITION FIXME WRITE A BOX</div>");
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);
66 if (!strncasecmp(buf, "time=", 5)) {
70 localtime_r(&tt, &tm);
71 strftime(datetime, sizeof datetime, "%c", &tm);
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);
82 if (!strncasecmp(buf, "Content-type:", 13)) {
83 strcpy(content_type, &buf[13]);
84 striplt(content_type);
87 raw_msg = ctdl_readtextmsg(c);
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
107 // These are the encodings we know how to handle. Decode in-place.
109 if (!strcasecmp(content_transfer_encoding, "base64")) {
110 StrBufDecodeBase64(raw_msg);
112 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
113 StrBufDecodeQP(raw_msg);
115 // At this point, raw_msg contains the decoded message.
116 // Now run through the renderers we have available.
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);
125 sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
127 FreeStrBuf(&raw_msg);
129 // If sanitized_msg is not NULL, we have rendered the message and can output it.
132 StrBufAppendBuf(sj, sanitized_msg, 0);
133 FreeStrBuf(&sanitized_msg);
137 StrBufAppendPrintf(sj, "</div>"); // end body
138 StrBufAppendPrintf(sj, "</div>"); // end content
139 StrBufAppendPrintf(sj, "</div>"); // end wrapper
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?
146 // Threaded view (recursive section)
148 void thread_o_print(struct ctdlsession *c, StrBuf * sj, struct mthread *m, int num_msgs, int where_parent_is, int nesting_level)
154 for (i = 0; i < num_msgs; ++i) {
155 if (m[i].parent == where_parent_is) {
157 if (++num_printed == 1) {
158 StrBufAppendPrintf(sj, "<ul style=\"list-style-type: none;\">");
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");
165 thread_o_print(c, sj, m, num_msgs, i, nesting_level + 1);
170 if (num_printed > 0) {
171 StrBufAppendPrintf(sj, "</ul>");
176 // Threaded view (entry point)
178 void threaded_view(struct http_transaction *h, struct ctdlsession *c, char *which)
187 ctdl_printf(c, "MSGS ALL|||9"); // 9 == headers + thread references
188 ctdl_readline(c, buf, sizeof(buf));
194 StrBuf *sj = NewStrBuf();
195 StrBufAppendPrintf(sj, "<html><body>\r\n");
197 while (ctdl_readline(c, buf, sizeof buf), strcmp(buf, "000")) {
200 if (num_msgs > num_alloc) {
201 if (num_alloc == 0) {
203 m = malloc(num_alloc * sizeof(struct mthread));
206 m = realloc(m, (num_alloc * sizeof(struct mthread)));
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);
220 while ((t = strtok_r(r, ",", &r))) {
222 m[num_msgs - 1].refhashes[0] = atoi(t); // always keep the first one
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);
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) {
244 setup_for_forum_view(c);
245 thread_o_print(c, sj, m, num_msgs, 0, 0); // Render threads recursively and recursively
247 // Garbage collection is for people who aren't smart enough to manage their own memory.
252 StrBufAppendPrintf(sj, "</body></html>\r\n");
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);
263 // flat view (entry point)
265 void flat_view(struct http_transaction *h, struct ctdlsession *c, char *which)
267 StrBuf *sj = NewStrBuf();
268 StrBufAppendPrintf(sj, "<html><body>\r\n");
270 setup_for_forum_view(c);
271 long *msglist = get_msglist(c, "ALL");
274 for (i = 0; (msglist[i] > 0); ++i) {
275 forum_render_one_message(c, sj, msglist[i]);
280 StrBufAppendPrintf(sj, "</body></html>\r\n");
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);
291 // render one message (entire transaction) FIXME EXTERMINATE
293 void html_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
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);
310 // Fetch a single message and return it in JSON format for client-side rendering
312 void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
314 StrBuf *raw_msg = NULL;
315 StrBuf *sanitized_msg = NULL;
317 char content_transfer_encoding[1024] = { 0 };
318 char content_type[1024] = { 0 };
319 char author[128] = { 0 };
320 char datetime[128] = { 0 };
322 setup_for_forum_view(c);
324 ctdl_printf(c, "MSG4 %ld", msgnum);
325 ctdl_readline(c, buf, sizeof(buf));
331 JsonValue *j = NewJsonObject(HKEY("message"));
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));
338 if (!strncasecmp(buf, "rfca=", 5)) {
339 JsonObjectAppend(j, NewJsonPlainString(HKEY("from"), &buf[5], -1));
341 if (!strncasecmp(buf, "time=", 5)) {
345 localtime_r(&tt, &tm);
346 strftime(datetime, sizeof datetime, "%c", &tm);
347 JsonObjectAppend(j, NewJsonPlainString(HKEY("time"), datetime, -1));
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);
358 if (!strncasecmp(buf, "Content-type:", 13)) {
359 strcpy(content_type, &buf[13]);
360 striplt(content_type);
363 raw_msg = ctdl_readtextmsg(c);
370 // These are the encodings we know how to handle. Decode in-place.
372 if (!strcasecmp(content_transfer_encoding, "base64")) {
373 StrBufDecodeBase64(raw_msg);
375 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
376 StrBufDecodeQP(raw_msg);
378 // At this point, raw_msg contains the decoded message.
379 // Now run through the renderers we have available.
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);
388 sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
390 FreeStrBuf(&raw_msg);
392 // If sanitized_msg is not NULL, we have rendered the message and can output it.
395 JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
399 StrBuf *sj = NewStrBuf();
400 SerializeJson(sj, j, 1); // '1' == free the source object
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);