example: fill in homepage
[rb-79.git] / write-thread.c
blobcd1a157fda997eee76d0ee4f88cb64125b6626e9
1 /*
2 * Copyright (c) 2017, De Rais <derais@cock.li>
4 * Permission to use, copy, modify, and/or distribute this software for
5 * any purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all
7 * copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 * PERFORMANCE OF THIS SOFTWARE.
18 #include <errno.h>
19 #include <stddef.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/stat.h>
24 #include <time.h>
25 #include <unistd.h>
27 #define PCRE2_CODE_UNIT_WIDTH 8
28 #include <pcre2.h>
30 #include "macros.h"
31 #include "rb79.h"
33 /* Global configuration */
34 const struct configuration *conf;
36 /* A template for pages 1, 2, ... of every board */
37 static char *board_page;
38 static size_t board_page_len;
40 /* A template for a particular thread's page */
41 static char *thread_page;
42 static size_t thread_page_len;
44 /* PCRE2 for parsing templates (pull out ${FOO}s) */
45 static pcre2_code *variables;
47 #define CHECK_FPRINTF(...) \
48 do { \
49 if (fprintf(__VA_ARGS__) < 0) { \
50 PERROR_MESSAGE("fprintf"); \
51 goto done; \
52 } \
53 } while (0)
56 * Read in the contents of ${static_www_folder}/_templates/${local_name}
57 * to *out.
59 * Preconditions:
61 * - ${static_www_folder}/_templates/${local_name} is a readable folder.
63 * - out is not 0.
65 * - Overwriting *out shall not cause a memory leak.
67 * Postconditions (success):
69 * - *out contains the full contents of
70 * ${static_www_folder}/_templates/${local_name}.
72 static int slurp_file(const char *local_name, char **out)
74 int ret = -1;
75 FILE *f = 0;
76 char *path = 0;
77 size_t path_len = 0;
78 size_t len = 0;
80 /* Load board_page */
81 path_len = snprintf(0, 0, "%s/_templates/%s", conf->static_www_folder,
82 local_name);
84 if (!(path = malloc(path_len + 1))) {
85 PERROR_MESSAGE("malloc");
86 goto done;
89 sprintf(path, "%s/_templates/%s", conf->static_www_folder, local_name);
91 if (!(f = fopen(path, "r"))) {
92 ERROR_MESSAGE("cannot open \"%s\"", path);
93 PERROR_MESSAGE("fopen");
94 goto done;
97 fseek(f, 0, SEEK_END);
98 len = ftell(f);
100 if (!(*out = malloc(len + 1))) {
101 PERROR_MESSAGE("malloc");
102 goto done;
105 fseek(f, 0, SEEK_SET);
107 /* XXX: can fread return short except on failure? */
108 if (fread(*out, 1, len, f) < len) {
109 PERROR_MESSAGE("fread");
110 goto done;
113 ret = 0;
114 done:
116 if (f) {
117 fclose(f);
120 free(path);
122 return ret;
126 * Initialize any static elements needed for this file
128 * Preconditions:
130 * - setup_write_thread() was not invoked more recently than
131 * clean_write_thread().
133 * Postconditions (success):
135 * - Any other function in this file may be safely called.
137 int setup_write_thread(const struct configuration *in_conf)
139 conf = in_conf;
141 if (slurp_file("board_page", &board_page) < 0) {
142 return -1;
145 board_page_len = strlen(board_page);
147 if (slurp_file("thread_page", &thread_page) < 0) {
148 return -1;
151 thread_page_len = strlen(thread_page);
152 int err_code = 0;
153 PCRE2_SIZE err_offset = 0;
154 PCRE2_UCHAR8 err_buf[120];
155 const char *variable_pattern_str = "[$][{](?<var>[^}]+)[}]";
157 if (!(variables = pcre2_compile((PCRE2_SPTR8) variable_pattern_str,
158 PCRE2_ZERO_TERMINATED, PCRE2_UTF,
159 &err_code, &err_offset, 0))) {
160 pcre2_get_error_message(err_code, err_buf, 120);
161 ERROR_MESSAGE("pcre2_compile: error with pattern \"%s\": %s",
162 variable_pattern_str, err_buf);
164 return -1;
167 return 0;
171 * Delete files which are in static_www_folder. Note that "full"
172 * means "the full file", not "the full path to the file". This is
173 * a defect.
175 * Preconditions:
177 * - system_full_path is a string of length system_full_path_len,
178 * something like "/m/src/4921183834.png".
180 * - system_thumb_path is a string of length system_thumb_path_len,
181 * something like "/m/src/4921183834.png".
183 * - As an exception, system_full_path and system_thumb_path may
184 * be 0 if their respective lengths are 0.
186 * - There is at most one board to which system_full_path and
187 * system_thumb_path are associated. For that board, THE LOCK
188 * IS HELD.
190 * Postconditions (success):
192 * - Those files have been unlink()'d.
194 int wt_remove_files(const char *system_full_path, size_t system_full_path_len,
195 const char *system_thumb_path, size_t system_thumb_path_len)
197 int ret = -1;
198 char *abs_full_path = 0;
199 size_t abs_full_path_len = 0;
200 char *abs_thumb_path = 0;
201 size_t abs_thumb_path_len = 0;
202 size_t j = 0;
204 if (!system_full_path_len) {
205 goto done_with_full;
208 abs_full_path_len = snprintf(0, 0, "%s%s", conf->static_www_folder,
209 system_full_path);
211 if (!(abs_full_path = malloc(abs_full_path_len + 1))) {
212 PERROR_MESSAGE("malloc");
213 goto done;
216 sprintf(abs_full_path, "%s%s", conf->static_www_folder,
217 system_full_path);
219 if (unlink(abs_full_path) < 0) {
220 PERROR_MESSAGE("unlink");
221 ERROR_MESSAGE("Failed to unlink(\"%s\")", abs_full_path);
222 goto done;
225 done_with_full:
227 if (!system_thumb_path_len) {
228 goto done_with_thumb;
231 for (j = 0; j < conf->filetypes_num; ++j) {
232 const struct filetype *f = &conf->filetypes[j];
234 if (f->static_thumbnail &&
235 !strcmp(f->static_thumbnail, system_thumb_path)) {
236 goto done_with_thumb;
240 abs_thumb_path_len = snprintf(0, 0, "%s%s", conf->static_www_folder,
241 system_thumb_path);
243 if (!(abs_thumb_path = malloc(abs_thumb_path_len + 1))) {
244 PERROR_MESSAGE("malloc");
245 goto done;
248 sprintf(abs_thumb_path, "%s%s", conf->static_www_folder,
249 system_thumb_path);
251 if (unlink(abs_thumb_path) < 0) {
252 PERROR_MESSAGE("unlink");
253 ERROR_MESSAGE("Failed to unlink(\"%s\")", abs_thumb_path);
254 goto done;
257 done_with_thumb:
258 ret = 0;
259 done:
260 free(abs_full_path);
261 free(abs_thumb_path);
263 return ret;
267 * Delete files which are in static_www_folder. Note that "full"
268 * means "the full file", not "the full path to the file". This is
269 * a defect.
271 * Preconditions:
273 * - board_idx represents a board, AND THE LOCK IS HELD.
275 * - There is a page for thread_id up, e.g. at
276 * ${static_www_folder}/m/res/${thread_id}/index.html.
278 * Postconditions (success):
280 * - The folder for thread id (e.g.
281 * ${static_www_folder}/m/res/${thread_id}) no longer exists.
283 int wt_remove_thread_page(size_t board_idx, uintmax_t thread_id)
285 int ret = -1;
286 char *index_path = 0;
287 char *folder_path = 0;
288 size_t len = snprintf(0, 0, "%s/%s/res/%ju/index.html",
289 conf->static_www_folder,
290 conf->boards[board_idx].name, thread_id);
292 if (!(index_path = malloc(len + 1))) {
293 PERROR_MESSAGE("malloc");
294 goto done;
297 sprintf(index_path, "%s/%s/res/%ju/index.html", conf->static_www_folder,
298 conf->boards[board_idx].name, thread_id);
300 if (unlink(index_path) < 0) {
301 PERROR_MESSAGE("unlink");
302 ERROR_MESSAGE("Failed to unlink(\"%s\")", index_path);
303 goto done;
306 if (!(folder_path = malloc(len + 1))) {
307 PERROR_MESSAGE("malloc");
308 goto done;
311 sprintf(folder_path, "%s/%s/res/%ju", conf->static_www_folder,
312 conf->boards[board_idx].name, thread_id);
314 if (rmdir(folder_path) < 0) {
315 PERROR_MESSAGE("rmdir");
316 ERROR_MESSAGE("Failed to rmdir(\"%s\")", folder_path);
317 goto done;
320 ret = 0;
321 done:
322 free(index_path);
323 free(folder_path);
325 return ret;
329 * Write out the HTML file for a board
331 * Preconditions:
333 * - setup_write_thread() has been called more recently than
334 * clean_write_thread().
336 * - board_idx represents a board, AND THE LOCK IS HELD.
338 * - thread_ids is an array of length thread_ids_num.
340 * - Each element of thread_ids represents a thread.
342 * - thread_ids is sorted by bump order, most recent thread first.
344 * Postconditions (success):
346 * - Files at ${static_www_folder}/${board}/${n}/index.html
347 * has been written out, representing the current state of the
348 * board thread, for n = 0 to however many there should be.
350 int wt_write_board(size_t board_idx, uintmax_t *thread_ids, size_t
351 thread_ids_num, size_t board_pages_num)
353 int ret = -1;
354 FILE *f = 0;
355 size_t len = 0;
356 size_t j = 0;
357 pcre2_match_data *match_data = 0;
358 PCRE2_UCHAR *var_name = 0;
359 uint_fast8_t more_left_to_delete = 1;
360 char *path = 0;
362 /* 3 capture groups: more than sufficient for ${foo} */
363 if (!(match_data = pcre2_match_data_create(3, 0))) {
364 PERROR_MESSAGE("pcre2_match_data_create");
365 goto done;
368 len = snprintf(0, 0, "%s/%s/%zu/index.html", conf->static_www_folder,
369 conf->boards[board_idx].name, (size_t) -1);
371 if (!(path = malloc(len + 1))) {
372 PERROR_MESSAGE("malloc");
373 goto done;
376 /* First, delete all the pages we won't need */
379 * XXX: should we really be rmdir()ing? Can't we just leave
380 * the old directories around?
382 j = board_pages_num ? board_pages_num : 1;
384 while (j != (size_t) -1 &&
385 more_left_to_delete) {
386 sprintf(path, "%s/%s/%zu/index.html", conf->static_www_folder,
387 conf->boards[board_idx].name, j);
389 if (unlink(path) < 0) {
390 if (errno == ENOENT) {
391 more_left_to_delete = 0;
392 break;
395 PERROR_MESSAGE("unlink");
396 ERROR_MESSAGE("Failed to unlink(\"%s\")", path);
397 goto done;
400 sprintf(path, "%s/%s/%zu", conf->static_www_folder,
401 conf->boards[board_idx].name, j);
403 if (rmdir(path) < 0) {
404 PERROR_MESSAGE("rmdir");
405 ERROR_MESSAGE("Failed to rmdir(\"%s\")", path);
406 goto done;
409 j++;
412 /* Now build the pages we do need */
413 for (size_t current_page = 0; !current_page ||
414 (current_page < board_pages_num); ++current_page) {
415 size_t idx = 0;
416 size_t match_pos = 0;
417 size_t after_match_pos = 0;
418 int nret = 0;
419 PCRE2_SIZE var_name_len = 0;
420 size_t challenge_id = rand() % conf->challenges_num;
421 size_t first_thread_idx = current_page *
422 conf->boards[board_idx].
423 threads_per_page;
424 size_t num_threads_this_page =
425 conf->boards[board_idx].threads_per_page;
427 if (first_thread_idx + num_threads_this_page >=
428 thread_ids_num) {
429 num_threads_this_page = thread_ids_num -
430 first_thread_idx;
433 if (current_page) {
434 sprintf(path, "%s/%s/%zu", conf->static_www_folder,
435 conf->boards[board_idx].name, current_page);
436 errno = 0;
438 /* Make the directory. We shouldn't need mkdir -p. */
439 if (mkdir(path, 0755) < 0) {
440 if (errno != EEXIST) {
441 PERROR_MESSAGE("mkdir");
442 ERROR_MESSAGE("mkdir(\"%s\") failed",
443 path);
444 goto done;
448 /* Now open the file */
449 sprintf(path, "%s/%s/%zu/index.html",
450 conf->static_www_folder,
451 conf->boards[board_idx].name,
452 current_page);
453 } else {
454 sprintf(path, "%s/%s/index.html",
455 conf->static_www_folder,
456 conf->boards[board_idx].name);
459 if (f) {
460 fclose(f);
463 if (!(f = fopen(path, "w"))) {
464 PERROR_MESSAGE("fopen");
465 ERROR_MESSAGE("fopen(\"%s\", \"w\") failed", path);
466 goto done;
469 find_next_bit:
471 /* Are we done? */
472 if (idx >= board_page_len) {
473 goto success;
476 /* Where does ${FOO} appear next? */
477 nret = pcre2_match(variables, (PCRE2_SPTR) board_page,
478 board_page_len, idx, 0, match_data, 0);
480 if (nret == PCRE2_ERROR_NOMATCH) {
481 CHECK_FPRINTF(f, "%s", board_page + idx);
482 goto success;
485 if (nret < 0) {
486 PCRE2_UCHAR8 err_buf[120];
488 pcre2_get_error_message(nret, err_buf, 120);
489 ERROR_MESSAGE(
490 "pcre2_match: error while matching \"%.*s\": %s"
491 " (PCRE2 %d)", (int) (board_page_len - idx),
492 board_page + idx, err_buf, nret);
493 goto done;
496 /* What is FOO? */
497 pcre2_substring_free(var_name);
498 var_name = 0;
499 var_name_len = 0;
501 if ((nret = pcre2_substring_get_byname(match_data,
502 (PCRE2_SPTR) "var",
503 &var_name,
504 &var_name_len))) {
505 PCRE2_UCHAR8 err_buf[120];
507 pcre2_get_error_message(nret, err_buf, 120);
508 ERROR_MESSAGE(
509 "pcre2_substring_get_byname: %s (PCRE2 %d)",
510 err_buf,
511 nret);
512 goto done;
515 match_pos = pcre2_get_ovector_pointer(match_data)[0];
516 after_match_pos = pcre2_get_ovector_pointer(match_data)[1];
517 CHECK_FPRINTF(f, "%.*s", (int) (match_pos - idx), board_page +
518 idx);
519 idx = match_pos;
521 if (!strncasecmp((const char *) var_name, "BOARD",
522 var_name_len)) {
523 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].name);
524 } else if (!strncasecmp((const char *) var_name, "BOARD_TITLE",
525 var_name_len)) {
526 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].title);
527 } else if (!strncasecmp((const char *) var_name, "CHALLENGE",
528 var_name_len)) {
529 CHECK_FPRINTF(f, "%s",
530 conf->challenges[challenge_id].question);
531 } else if (!strncasecmp((const char *) var_name, "CHALLENGE_ID",
532 var_name_len)) {
533 CHECK_FPRINTF(f, "%zu", challenge_id);
534 } else if (!strncasecmp((const char *) var_name, "PAGELINKS",
535 var_name_len)) {
536 CHECK_FPRINTF(f, "[<a href=\"/%s\">0</a>] ",
537 conf->boards[board_idx].name);
539 for (size_t j = 1; j < board_pages_num; ++j) {
540 CHECK_FPRINTF(f,
541 "[<a href=\"/%s/%zu\">%zu</a>] ",
542 conf->boards[board_idx].name, j,
545 } else if (!strncasecmp((const char *) var_name,
546 "RANDOM_HEADER", var_name_len)) {
547 CHECK_FPRINTF(f, "%s", conf->headers[rand() %
548 conf->headers_num]);
549 } else if (!strncasecmp((const char *) var_name, "THREADS",
550 var_name_len)) {
552 * We have to transfer control to db-ZZZ
553 * for a bit - it comes back through
554 * wt_write_post().
556 db_writeback_thread_summaries(board_idx, thread_ids +
557 first_thread_idx,
558 num_threads_this_page, f);
559 } else {
560 CHECK_FPRINTF(f, "${%.*s}", (int) var_name_len,
561 var_name);
564 idx = after_match_pos;
565 goto find_next_bit;
566 success:
570 ret = 0;
571 done:
572 pcre2_match_data_free(match_data);
573 pcre2_substring_free(var_name);
575 if (f) {
576 fclose(f);
579 free(path);
581 return ret;
585 * Write p out to f as the first post of a thread.
587 * This function is intended to be called by
588 * db_writeback_posts_in_thread(), which is called from wt_write_thread().
590 * Preconditions:
592 * - setup_write_thread() has been called more recently than
593 * clean_write_thread().
595 * - p is a fully filled stored_post (i.e. from a row in the database).
597 * - f is an open, writable FILE *.
599 * - board_idx represents a baord.
601 * - If is_summary is not zero, then board_name is a string like
602 * "m" or "sf". Furthermore, if is_op is also not zero, then
603 * total_posts_in_thread is a number suitable for describing the
604 * thread.
606 * Postconditions (success):
608 * - the contents of p have been written to f, which returned no
609 * errors. The format is not precisely defined.
611 int wt_write_post(struct prepared_post *p, FILE *f, uint_fast8_t is_op,
612 uint_fast8_t is_summary, const char *board_name, uintmax_t
613 total_posts_in_thread, uint_fast8_t hr_before)
615 int ret = -1;
616 char *human_readable_time = util_iso8601_from_time_t(p->now);
618 if (!human_readable_time) {
619 ERROR_MESSAGE("util_iso_8601_from_time(%ju) failed",
620 (uintmax_t) p->now);
623 if (hr_before) {
624 /* XXX: make sure this looks decent, both in ff and elinks */
625 CHECK_FPRINTF(f, "<hr />");
628 if (is_op) {
629 CHECK_FPRINTF(f, "<div class=\"op-post\" id=\"post%ju\">",
630 p->id);
631 } else {
632 CHECK_FPRINTF(f, "<div class=\"reply-post\" id=\"post%ju\">",
633 p->id);
636 CHECK_FPRINTF(f, "<table><tr>");
637 CHECK_FPRINTF(f, "<td colspan=\"2\">");
638 CHECK_FPRINTF(f, "<div class=\"post-info\">");
640 if (p->subject_len) {
641 CHECK_FPRINTF(f, "<span class=\"post-subject\">%.*s</span> ",
642 (int) p->subject_len, p->subject);
645 if (p->name_len) {
646 if (p->email_len) {
647 CHECK_FPRINTF(f, "<a href=\"mailto:%.*s\">",
648 (int) p->email_len, p->email);
651 CHECK_FPRINTF(f, "<span class=\"post-author\">%.*s</span>",
652 (int) p->name_len, p->name);
654 if (p->email_len) {
655 CHECK_FPRINTF(f, "</a>");
658 if (p->tripcode_len) {
659 CHECK_FPRINTF(f, "<span class=\"post-tripcode\">");
660 CHECK_FPRINTF(f, "!%.*s</span>", (int) p->tripcode_len,
661 p->tripcode);
665 CHECK_FPRINTF(f, " <span class=\"post-time\">%s</span>",
666 human_readable_time ? human_readable_time : "NEVER");
667 CHECK_FPRINTF(f, " <span class=\"post-number\">No. %ju</span>", p->id);
669 if (is_op) {
670 if (p->thread_closed) {
671 CHECK_FPRINTF(f, " <span class=\"thread-closed\">");
672 CHECK_FPRINTF(f, "Closed</span>");
675 if (p->thread_stickied) {
676 CHECK_FPRINTF(f, " <span class=\"thread-stickied\">");
677 CHECK_FPRINTF(f, "Sticky</span>");
681 if (is_summary &&
682 is_op) {
683 CHECK_FPRINTF(f, " [<a href=\"/%s/res/%ju\">View</a>]",
684 board_name, p->id);
687 CHECK_FPRINTF(f, "</div></td></tr>");
689 if (p->system_full_path_len) {
690 CHECK_FPRINTF(f, "<tr><td colspan=\"2\">");
691 CHECK_FPRINTF(f, "<span class=\"file-info\">");
692 CHECK_FPRINTF(f, "<a class=\"filelink\" href=\"%.*s\">",
693 (int) p->system_full_path_len,
694 p->system_full_path);
695 CHECK_FPRINTF(f, "%.*s", (int) p->file_name_len, p->file_name);
696 CHECK_FPRINTF(f, "</a>");
697 CHECK_FPRINTF(f, " (%.*s)</span></td></tr><tr>",
698 (int) p->file_info_len, p->file_info);
699 CHECK_FPRINTF(f, "<td class=\"thin\">");
700 CHECK_FPRINTF(f, "<a class=\"filelink\" href=\"%.*s\">",
701 (int) p->system_full_path_len,
702 p->system_full_path);
703 CHECK_FPRINTF(f, "<img src=\"%.*s\" alt=\"image\" ",
704 (int) p->system_thumb_path_len,
705 p->system_thumb_path);
706 CHECK_FPRINTF(f, "class=\"thumb\"/>");
707 CHECK_FPRINTF(f, "</a></td>");
708 } else {
709 CHECK_FPRINTF(f, "<tr><td class=\"thin\">&nbsp;</td>");
712 CHECK_FPRINTF(f, "<td>");
713 CHECK_FPRINTF(f, "<div class=\"post-comment\">");
714 CHECK_FPRINTF(f, "%.*s", (int) p->comment_len, p->comment);
715 CHECK_FPRINTF(f, "</div>");
716 CHECK_FPRINTF(f, "</td></tr></table></div>");
718 if (is_summary &&
719 is_op) {
720 CHECK_FPRINTF(f, "<div class=\"reply-count-info\">");
721 CHECK_FPRINTF(f, "%ju post%s in thread. ",
722 total_posts_in_thread, (total_posts_in_thread ==
723 1) ? "" : "s");
724 CHECK_FPRINTF(f, "<a href=\"/%s/res/%ju\">View thread</a>",
725 board_name, p->id);
726 CHECK_FPRINTF(f, "</div>");
729 ret = 0;
730 done:
731 free(human_readable_time);
733 return ret;
737 * Write out the HTML file for a thread
739 * Preconditions:
741 * - setup_write_thread() has been called more recently than
742 * clean_write_thread().
744 * - board_idx represents a board, AND THE LOCK IS HELD.
746 * - thread is the post_id of a thread.
748 * Postconditions (success):
750 * - A file at ${static_www_folder}/${board}/res/${thread}/index.html
751 * has been written out, representing the recent state of the
752 * thread.
754 int wt_write_thread(size_t board_idx, uintmax_t thread)
756 int ret = -1;
757 size_t idx = 0;
758 size_t match_pos = 0;
759 size_t after_match_pos = 0;
760 pcre2_match_data *match_data = 0;
761 int nret = 0;
762 FILE *f = 0;
763 size_t len = 0;
764 char *index_path = 0;
765 PCRE2_UCHAR *var_name = 0;
766 PCRE2_SIZE var_name_len = 0;
767 size_t challenge_id = rand() % conf->challenges_num;
769 len = snprintf(0, 0, "%s/%s/res/%ju/index.html",
770 conf->static_www_folder, conf->boards[board_idx].name,
771 thread);
773 if (!(index_path = malloc(len + 1))) {
774 PERROR_MESSAGE("malloc");
775 goto done;
778 sprintf(index_path, "%s/%s/res/%ju", conf->static_www_folder,
779 conf->boards[board_idx].name, thread);
780 errno = 0;
782 /* Make the directory. We shouldn't need mkdir -p. */
783 if (mkdir(index_path, 0755) < 0) {
784 if (errno != EEXIST) {
785 PERROR_MESSAGE("mkdir");
786 ERROR_MESSAGE("mkdir(\"%s\") failed", index_path);
787 goto done;
791 /* Now open the file */
792 sprintf(index_path, "%s/%s/res/%ju/index.html", conf->static_www_folder,
793 conf->boards[board_idx].name, thread);
795 if (!(f = fopen(index_path, "w"))) {
796 PERROR_MESSAGE("fopen");
797 ERROR_MESSAGE("fopen(\"%s\", \"w\") failed", index_path);
798 goto done;
801 /* 3 capture groups: more than sufficient for ${foo} */
802 if (!(match_data = pcre2_match_data_create(3, 0))) {
803 PERROR_MESSAGE("pcre2_match_data_create");
804 goto done;
807 find_next_bit:
809 /* Are we done? */
810 if (idx >= thread_page_len) {
811 goto success;
814 /* Where does ${FOO} appear next? */
815 nret = pcre2_match(variables, (PCRE2_SPTR) thread_page, thread_page_len,
816 idx, 0, match_data, 0);
818 if (nret == PCRE2_ERROR_NOMATCH) {
819 CHECK_FPRINTF(f, "%s", thread_page + idx);
820 goto success;
823 if (nret < 0) {
824 PCRE2_UCHAR8 err_buf[120];
826 pcre2_get_error_message(nret, err_buf, 120);
827 ERROR_MESSAGE("pcre2_match: error while matching \"%.*s\": %s"
828 " (PCRE2 %d)", (int) (thread_page_len - idx),
829 thread_page + idx,
830 err_buf, nret);
831 goto done;
834 /* What is FOO? */
835 pcre2_substring_free(var_name);
836 var_name = 0;
837 var_name_len = 0;
839 if ((nret = pcre2_substring_get_byname(match_data, (PCRE2_SPTR) "var",
840 &var_name, &var_name_len))) {
841 PCRE2_UCHAR8 err_buf[120];
843 pcre2_get_error_message(nret, err_buf, 120);
844 ERROR_MESSAGE("pcre2_substring_get_byname: %s (PCRE2 %d)",
845 err_buf, nret);
846 goto done;
849 match_pos = pcre2_get_ovector_pointer(match_data)[0];
850 after_match_pos = pcre2_get_ovector_pointer(match_data)[1];
851 CHECK_FPRINTF(f, "%.*s", (int) (match_pos - idx), thread_page + idx);
852 idx = match_pos;
854 if (!strncasecmp((const char *) var_name, "BOARD", var_name_len)) {
855 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].name);
856 } else if (!strncasecmp((const char *) var_name, "BOARD_TITLE",
857 var_name_len)) {
858 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].title);
859 } else if (!strncasecmp((const char *) var_name, "CHALLENGE",
860 var_name_len)) {
861 CHECK_FPRINTF(f, "%s", conf->challenges[challenge_id].question);
862 } else if (!strncasecmp((const char *) var_name, "CHALLENGE_ID",
863 var_name_len)) {
864 CHECK_FPRINTF(f, "%zu", challenge_id);
865 } else if (!strncasecmp((const char *) var_name, "OP_SUBJECT",
866 var_name_len)) {
867 char *subject = 0;
868 size_t subject_len = 0;
870 if (db_extract_subject(board_idx, thread, &subject,
871 &subject_len) < 0) {
872 goto done;
875 if (subject) {
876 CHECK_FPRINTF(f, "%.*s", (int) subject_len, subject);
879 free(subject);
880 } else if (!strncasecmp((const char *) var_name, "RANDOM_HEADER",
881 var_name_len)) {
882 CHECK_FPRINTF(f, "%s", conf->headers[rand() %
883 conf->headers_num]);
884 } else if (!strncasecmp((const char *) var_name, "THREAD_NUMBER",
885 var_name_len)) {
886 CHECK_FPRINTF(f, "%zu", thread);
887 } else if (!strncasecmp((const char *) var_name, "POSTS",
888 var_name_len)) {
890 * We have to transfer control to db-ZZZ for a bit
891 * - it comes back through wt_write_post().
893 db_writeback_posts_in_thread(board_idx, thread, f,
894 wt_write_post);
895 } else {
896 CHECK_FPRINTF(f, "${%.*s}", (int) var_name_len, var_name);
899 idx = after_match_pos;
900 goto find_next_bit;
901 success:
902 ret = 0;
903 done:
904 pcre2_match_data_free(match_data);
905 pcre2_substring_free(var_name);
907 if (f) {
908 fclose(f);
911 free(index_path);
913 return ret;
917 * Clean any memory from this file
919 * Postconditions (success):
921 * - Valgrind won't report any memory leaks from this file.
923 * - setup_write_thread() can be safely called again.
925 void clean_write_thread(void)
927 conf = 0;
928 free(board_page);
929 free(thread_page);
930 board_page = 0;
931 board_page_len = 0;
932 thread_page = 0;
933 thread_page_len = 0;
934 pcre2_code_free(variables);
935 variables = 0;