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);
95 StrBufAppendPrintf(sj, "<div>"); // begin message wrapper
96 StrBufAppendPrintf(sj, "<div style=\"float:left;padding-right:2px\">"); // begin avatar FIXME move the style to a stylesheet
97 StrBufAppendPrintf(sj, "<i class=\"fa fa-user-circle fa-2x\"></i> "); // FIXME temporary avatar
98 StrBufAppendPrintf(sj, "</div>"); // end avatar
99 StrBufAppendPrintf(sj, "<div>"); // begin content
100 StrBufAppendPrintf(sj, "<div>"); // begin header
101 StrBufAppendPrintf(sj, "<span class=\"ctdl-username\"><a href=\"#\">%s</a></span> ", author); // FIXME link to user profile or whatever
102 StrBufAppendPrintf(sj, "<span class=\"ctdl-msgdate\">%s</span> ", datetime);
103 StrBufAppendPrintf(sj, "</div>"); // end header
104 StrBufAppendPrintf(sj, "<div>"); // begin body
108 // These are the encodings we know how to handle. Decode in-place.
110 if (!strcasecmp(content_transfer_encoding, "base64")) {
111 StrBufDecodeBase64(raw_msg);
113 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
114 StrBufDecodeQP(raw_msg);
117 // At this point, raw_msg contains the decoded message.
118 // Now run through the renderers we have available.
120 if (!strncasecmp(content_type, "text/html", 9)) {
121 sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
123 else if (!strncasecmp(content_type, "text/plain", 10)) {
124 sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
126 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
127 sanitized_msg = variformat2html(raw_msg);
130 sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
132 FreeStrBuf(&raw_msg);
134 // If sanitized_msg is not NULL, we have rendered the message and can output it.
137 StrBufAppendBuf(sj, sanitized_msg, 0);
138 FreeStrBuf(&sanitized_msg);
142 StrBufAppendPrintf(sj, "</div>"); // end body
143 StrBufAppendPrintf(sj, "</div>"); // end content
144 StrBufAppendPrintf(sj, "</div>"); // end wrapper
148 // This code implements the thread display code. The thread sorting algorithm is working nicely but we're trying
149 // not to do rendering in the C server of webcit. Maybe move it into the server as "MSGS threaded" or something like that?
151 // Threaded view (recursive section)
153 void thread_o_print(struct ctdlsession *c, StrBuf *sj, struct mthread *m, int num_msgs, int where_parent_is, int nesting_level)
159 for (i=0; i<num_msgs; ++i) {
160 if (m[i].parent == where_parent_is) {
162 if (++num_printed == 1) {
163 StrBufAppendPrintf(sj, "<ul style=\"list-style-type: none;\">");
166 StrBufAppendPrintf(sj, "<li class=\"post\" id=\"post-%ld\">", m[i].msgnum);
167 forum_render_one_message(c, sj, m[i].msgnum);
168 StrBufAppendPrintf(sj, "</li>\r\n");
170 thread_o_print(c, sj, m, num_msgs, i, nesting_level+1);
175 if (num_printed > 0) {
176 StrBufAppendPrintf(sj, "</ul>");
181 // Threaded view (entry point)
183 void threaded_view(struct http_transaction *h, struct ctdlsession *c, char *which)
192 ctdl_printf(c, "MSGS ALL|||9"); // 9 == headers + thread references
193 ctdl_readline(c, buf, sizeof(buf));
199 StrBuf *sj = NewStrBuf();
200 StrBufAppendPrintf(sj, "<html><body>\r\n");
202 while (ctdl_readline(c, buf, sizeof buf), strcmp(buf,"000")) {
205 if (num_msgs > num_alloc) {
206 if (num_alloc == 0) {
208 m = malloc(num_alloc * sizeof(struct mthread));
212 m = realloc(m, (num_alloc * sizeof(struct mthread)));
216 memset(&m[num_msgs-1], 0, sizeof(struct mthread));
217 m[num_msgs-1].msgnum = extract_long(buf, 0);
218 m[num_msgs-1].datetime = extract_long(buf, 1);
219 extract_token(m[num_msgs-1].from, buf, 2, '|', sizeof m[num_msgs-1].from);
220 m[num_msgs-1].threadhash = extract_int(buf, 6);
221 extract_token(refs, buf, 7, '|', sizeof refs);
226 while ((t = strtok_r(r, ",", &r))) {
228 m[num_msgs-1].refhashes[0] = atoi(t); // always keep the first one
231 memcpy(&m[num_msgs-1].refhashes[1], &m[num_msgs-1].refhashes[2], sizeof(int)*8 ); // shift the rest
232 m[num_msgs-1].refhashes[9] = atoi(t);
239 // 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.
240 for (i=0; i<num_msgs; ++i) {
241 for (j=9; (j>=0)&&(m[i].parent==0); --j) {
242 for (k=0; (k<num_msgs)&&(m[i].parent==0); ++k) {
243 if (m[i].refhashes[j] == m[k].threadhash) {
251 setup_for_forum_view(c);
252 thread_o_print(c, sj, m, num_msgs, 0, 0); // Render threads recursively and recursively
254 // Garbage collection is for people who aren't smart enough to manage their own memory.
259 StrBufAppendPrintf(sj, "</body></html>\r\n");
261 add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
262 h->response_code = 200;
263 h->response_string = strdup("OK");
264 h->response_body_length = StrLength(sj);
265 h->response_body = SmashStrBuf(&sj);
270 // flat view (entry point)
272 void flat_view(struct http_transaction *h, struct ctdlsession *c, char *which)
274 StrBuf *sj = NewStrBuf();
275 StrBufAppendPrintf(sj, "<html><body>\r\n");
277 setup_for_forum_view(c);
278 long *msglist = get_msglist(c, "ALL");
281 for (i=0; (msglist[i] > 0); ++i) {
282 forum_render_one_message(c, sj, msglist[i]);
287 StrBufAppendPrintf(sj, "</body></html>\r\n");
289 add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
290 h->response_code = 200;
291 h->response_string = strdup("OK");
292 h->response_body_length = StrLength(sj);
293 h->response_body = SmashStrBuf(&sj);
298 // render one message (entire transaction) FIXME EXTERMINATE
300 void html_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
302 StrBuf *sj = NewStrBuf();
303 StrBufAppendPrintf(sj, "<html><body>\r\n");
304 setup_for_forum_view(c); // FIXME way too inefficient to do this for every message !!!!!!!!!!!!!
305 forum_render_one_message(c, sj, msgnum);
306 StrBufAppendPrintf(sj, "</body></html>\r\n");
307 add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
308 h->response_code = 200;
309 h->response_string = strdup("OK");
310 h->response_body_length = StrLength(sj);
311 h->response_body = SmashStrBuf(&sj);
317 // Fetch a single message and return it in JSON format for client-side rendering
319 void json_render_one_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
321 StrBuf *raw_msg = NULL;
322 StrBuf *sanitized_msg = NULL;
324 char content_transfer_encoding[1024] = { 0 };
325 char content_type[1024] = { 0 };
326 char author[128] = { 0 };
327 char datetime[128] = { 0 } ;
329 setup_for_forum_view(c);
331 ctdl_printf(c, "MSG4 %ld", msgnum);
332 ctdl_readline(c, buf, sizeof(buf));
338 JsonValue *j = NewJsonObject(HKEY("message"));
340 while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000")) ) {
341 // citadel header parsing here
342 if (!strncasecmp(buf, "from=", 5)) {
343 JsonObjectAppend(j, NewJsonPlainString( HKEY("from"), &buf[5], -1));
345 if (!strncasecmp(buf, "rfca=", 5)) {
346 JsonObjectAppend(j, NewJsonPlainString( HKEY("from"), &buf[5], -1));
348 if (!strncasecmp(buf, "time=", 5)) {
352 localtime_r(&tt, &tm);
353 strftime(datetime, sizeof datetime, "%c", &tm);
354 JsonObjectAppend(j, NewJsonPlainString( HKEY("time"), datetime, -1));
358 if (!strcmp(buf, "text")) {
359 while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000")) ) {
360 // rfc822 header parsing here
361 if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
362 strcpy(content_transfer_encoding, &buf[26]);
363 striplt(content_transfer_encoding);
365 if (!strncasecmp(buf, "Content-type:", 13)) {
366 strcpy(content_type, &buf[13]);
367 striplt(content_type);
370 raw_msg = ctdl_readtextmsg(c);
378 // These are the encodings we know how to handle. Decode in-place.
380 if (!strcasecmp(content_transfer_encoding, "base64")) {
381 StrBufDecodeBase64(raw_msg);
383 if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
384 StrBufDecodeQP(raw_msg);
387 // At this point, raw_msg contains the decoded message.
388 // Now run through the renderers we have available.
390 if (!strncasecmp(content_type, "text/html", 9)) {
391 sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
393 else if (!strncasecmp(content_type, "text/plain", 10)) {
394 sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
396 else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
397 sanitized_msg = variformat2html(raw_msg);
400 sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
402 FreeStrBuf(&raw_msg);
404 // If sanitized_msg is not NULL, we have rendered the message and can output it.
407 JsonObjectAppend(j, NewJsonString(HKEY("text"), sanitized_msg, NEWJSONSTRING_SMASHBUF));
411 StrBuf *sj = NewStrBuf();
412 SerializeJson(sj, j, 1); // '1' == free the source object
414 add_response_header(h, strdup("Content-type"), strdup("application/json"));
415 h->response_code = 200;
416 h->response_string = strdup("OK");
417 h->response_body_length = StrLength(sj);
418 h->response_body = SmashStrBuf(&sj);