server: check for overflow near malloc
[rb-79.git] / write-thread.c
blobd7b868b0d78f2ddc3c945bbff9e7ca98267acb5c
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_len + 1 < path_len) {
85 ERROR_MESSAGE("overflow");
86 goto done;
89 if (!(path = malloc(path_len + 1))) {
90 PERROR_MESSAGE("malloc");
91 goto done;
94 sprintf(path, "%s/_templates/%s", conf->static_www_folder, local_name);
96 if (!(f = fopen(path, "r"))) {
97 ERROR_MESSAGE("cannot open \"%s\"", path);
98 PERROR_MESSAGE("fopen");
99 goto done;
102 fseek(f, 0, SEEK_END);
103 len = ftell(f);
105 if (len + 1 < len) {
106 ERROR_MESSAGE("overflow");
107 goto done;
110 if (!(*out = malloc(len + 1))) {
111 PERROR_MESSAGE("malloc");
112 goto done;
115 fseek(f, 0, SEEK_SET);
117 /* XXX: can fread return short except on failure? */
118 if (fread(*out, 1, len, f) < len) {
119 PERROR_MESSAGE("fread");
120 goto done;
123 ret = 0;
124 done:
126 if (f) {
127 fclose(f);
130 free(path);
132 return ret;
136 * Initialize any static elements needed for this file
138 * Preconditions:
140 * - setup_write_thread() was not invoked more recently than
141 * clean_write_thread().
143 * Postconditions (success):
145 * - Any other function in this file may be safely called.
147 int setup_write_thread(const struct configuration *in_conf)
149 conf = in_conf;
151 if (slurp_file("board_page", &board_page) < 0) {
152 return -1;
155 board_page_len = strlen(board_page);
157 if (slurp_file("thread_page", &thread_page) < 0) {
158 return -1;
161 thread_page_len = strlen(thread_page);
162 int err_code = 0;
163 PCRE2_SIZE err_offset = 0;
164 PCRE2_UCHAR8 err_buf[120];
165 const char *variable_pattern_str = "[$][{](?<var>[^}]+)[}]";
167 if (!(variables = pcre2_compile((PCRE2_SPTR8) variable_pattern_str,
168 PCRE2_ZERO_TERMINATED, PCRE2_UTF,
169 &err_code, &err_offset, 0))) {
170 pcre2_get_error_message(err_code, err_buf, 120);
171 ERROR_MESSAGE("pcre2_compile: error with pattern \"%s\": %s",
172 variable_pattern_str, err_buf);
174 return -1;
177 return 0;
181 * Delete files which are in static_www_folder. Note that "full"
182 * means "the full file", not "the full path to the file". This is
183 * a defect.
185 * Preconditions:
187 * - system_full_path is a string of length system_full_path_len,
188 * something like "/m/src/4921183834.png".
190 * - system_thumb_path is a string of length system_thumb_path_len,
191 * something like "/m/src/4921183834.png".
193 * - As an exception, system_full_path and system_thumb_path may
194 * be 0 if their respective lengths are 0.
196 * - There is at most one board to which system_full_path and
197 * system_thumb_path are associated. For that board, THE LOCK
198 * IS HELD.
200 * Postconditions (success):
202 * - Those files have been unlink()'d.
204 int wt_remove_files(const char *system_full_path, size_t system_full_path_len,
205 const char *system_thumb_path, size_t system_thumb_path_len)
207 int ret = -1;
208 char *abs_full_path = 0;
209 size_t abs_full_path_len = 0;
210 char *abs_thumb_path = 0;
211 size_t abs_thumb_path_len = 0;
212 size_t j = 0;
214 if (!system_full_path_len) {
215 goto done_with_full;
218 abs_full_path_len = snprintf(0, 0, "%s%s", conf->static_www_folder,
219 system_full_path);
221 if (abs_full_path_len + 1 < abs_full_path_len) {
222 ERROR_MESSAGE("overflow");
223 goto done;
226 if (!(abs_full_path = malloc(abs_full_path_len + 1))) {
227 PERROR_MESSAGE("malloc");
228 goto done;
231 sprintf(abs_full_path, "%s%s", conf->static_www_folder,
232 system_full_path);
234 if (unlink(abs_full_path) < 0) {
235 PERROR_MESSAGE("unlink");
236 ERROR_MESSAGE("Failed to unlink(\"%s\")", abs_full_path);
237 goto done;
240 done_with_full:
242 if (!system_thumb_path_len) {
243 goto done_with_thumb;
246 for (j = 0; j < conf->filetypes_num; ++j) {
247 const struct filetype *f = &conf->filetypes[j];
249 if (f->static_thumbnail &&
250 !strcmp(f->static_thumbnail, system_thumb_path)) {
251 goto done_with_thumb;
255 abs_thumb_path_len = snprintf(0, 0, "%s%s", conf->static_www_folder,
256 system_thumb_path);
258 if (abs_thumb_path_len + 1 < abs_thumb_path_len) {
259 ERROR_MESSAGE("overflow");
260 goto done;
263 if (!(abs_thumb_path = malloc(abs_thumb_path_len + 1))) {
264 PERROR_MESSAGE("malloc");
265 goto done;
268 sprintf(abs_thumb_path, "%s%s", conf->static_www_folder,
269 system_thumb_path);
271 if (unlink(abs_thumb_path) < 0) {
272 PERROR_MESSAGE("unlink");
273 ERROR_MESSAGE("Failed to unlink(\"%s\")", abs_thumb_path);
274 goto done;
277 done_with_thumb:
278 ret = 0;
279 done:
280 free(abs_full_path);
281 free(abs_thumb_path);
283 return ret;
287 * Delete files which are in static_www_folder. Note that "full"
288 * means "the full file", not "the full path to the file". This is
289 * a defect.
291 * Preconditions:
293 * - board_idx represents a board, AND THE LOCK IS HELD.
295 * - There is a page for thread_id up, e.g. at
296 * ${static_www_folder}/m/res/${thread_id}/index.html.
298 * Postconditions (success):
300 * - The folder for thread id (e.g.
301 * ${static_www_folder}/m/res/${thread_id}) no longer exists.
303 int wt_remove_thread_page(size_t board_idx, uintmax_t thread_id)
305 int ret = -1;
306 char *index_path = 0;
307 char *folder_path = 0;
308 size_t len = snprintf(0, 0, "%s/%s/res/%ju/index.html",
309 conf->static_www_folder,
310 conf->boards[board_idx].name, thread_id);
312 if (len + 1 < len) {
313 ERROR_MESSAGE("overflow");
314 goto done;
317 if (!(index_path = malloc(len + 1))) {
318 PERROR_MESSAGE("malloc");
319 goto done;
322 sprintf(index_path, "%s/%s/res/%ju/index.html", conf->static_www_folder,
323 conf->boards[board_idx].name, thread_id);
325 if (unlink(index_path) < 0) {
326 PERROR_MESSAGE("unlink");
327 ERROR_MESSAGE("Failed to unlink(\"%s\")", index_path);
328 goto done;
331 if (!(folder_path = malloc(len + 1))) {
332 PERROR_MESSAGE("malloc");
333 goto done;
336 sprintf(folder_path, "%s/%s/res/%ju", conf->static_www_folder,
337 conf->boards[board_idx].name, thread_id);
339 if (rmdir(folder_path) < 0) {
340 PERROR_MESSAGE("rmdir");
341 ERROR_MESSAGE("Failed to rmdir(\"%s\")", folder_path);
342 goto done;
345 ret = 0;
346 done:
347 free(index_path);
348 free(folder_path);
350 return ret;
354 * Write out the HTML file for a board
356 * Preconditions:
358 * - setup_write_thread() has been called more recently than
359 * clean_write_thread().
361 * - board_idx represents a board, AND THE LOCK IS HELD.
363 * - thread_ids is an array of length thread_ids_num.
365 * - Each element of thread_ids represents a thread.
367 * - thread_ids is sorted by bump order, most recent thread first.
369 * Postconditions (success):
371 * - Files at ${static_www_folder}/${board}/${n}/index.html
372 * has been written out, representing the current state of the
373 * board thread, for n = 0 to however many there should be.
375 int wt_write_board(size_t board_idx, uintmax_t *thread_ids, size_t
376 thread_ids_num, size_t board_pages_num)
378 int ret = -1;
379 FILE *f = 0;
380 size_t len = 0;
381 size_t j = 0;
382 pcre2_match_data *match_data = 0;
383 PCRE2_UCHAR *var_name = 0;
384 uint_fast8_t more_left_to_delete = 1;
385 char *path = 0;
387 /* 3 capture groups: more than sufficient for ${foo} */
388 if (!(match_data = pcre2_match_data_create(3, 0))) {
389 PERROR_MESSAGE("pcre2_match_data_create");
390 goto done;
393 len = snprintf(0, 0, "%s/%s/%zu/index.html", conf->static_www_folder,
394 conf->boards[board_idx].name, (size_t) -1);
396 if (len + 1 < len) {
397 ERROR_MESSAGE("overflow");
398 goto done;
401 if (!(path = malloc(len + 1))) {
402 PERROR_MESSAGE("malloc");
403 goto done;
406 /* First, delete all the pages we won't need */
409 * XXX: should we really be rmdir()ing? Can't we just leave
410 * the old directories around?
412 j = board_pages_num ? board_pages_num : 1;
414 while (j != (size_t) -1 &&
415 more_left_to_delete) {
416 sprintf(path, "%s/%s/%zu/index.html", conf->static_www_folder,
417 conf->boards[board_idx].name, j);
419 if (unlink(path) < 0) {
420 if (errno == ENOENT) {
421 more_left_to_delete = 0;
422 break;
425 PERROR_MESSAGE("unlink");
426 ERROR_MESSAGE("Failed to unlink(\"%s\")", path);
427 goto done;
430 sprintf(path, "%s/%s/%zu", conf->static_www_folder,
431 conf->boards[board_idx].name, j);
433 if (rmdir(path) < 0) {
434 PERROR_MESSAGE("rmdir");
435 ERROR_MESSAGE("Failed to rmdir(\"%s\")", path);
436 goto done;
439 j++;
442 /* Now build the pages we do need */
443 for (size_t current_page = 0; !current_page ||
444 (current_page < board_pages_num); ++current_page) {
445 size_t idx = 0;
446 size_t match_pos = 0;
447 size_t after_match_pos = 0;
448 int nret = 0;
449 PCRE2_SIZE var_name_len = 0;
450 size_t challenge_id = rand() % conf->challenges_num;
451 size_t first_thread_idx = current_page *
452 conf->boards[board_idx].
453 threads_per_page;
454 size_t num_threads_this_page =
455 conf->boards[board_idx].threads_per_page;
457 if (first_thread_idx + num_threads_this_page >=
458 thread_ids_num) {
459 num_threads_this_page = thread_ids_num -
460 first_thread_idx;
463 if (current_page) {
464 sprintf(path, "%s/%s/%zu", conf->static_www_folder,
465 conf->boards[board_idx].name, current_page);
466 errno = 0;
468 /* Make the directory. We shouldn't need mkdir -p. */
469 if (mkdir(path, 0755) < 0) {
470 if (errno != EEXIST) {
471 PERROR_MESSAGE("mkdir");
472 ERROR_MESSAGE("mkdir(\"%s\") failed",
473 path);
474 goto done;
478 /* Now open the file */
479 sprintf(path, "%s/%s/%zu/index.html",
480 conf->static_www_folder,
481 conf->boards[board_idx].name,
482 current_page);
483 } else {
484 sprintf(path, "%s/%s/index.html",
485 conf->static_www_folder,
486 conf->boards[board_idx].name);
489 if (f) {
490 fclose(f);
493 if (!(f = fopen(path, "w"))) {
494 PERROR_MESSAGE("fopen");
495 ERROR_MESSAGE("fopen(\"%s\", \"w\") failed", path);
496 goto done;
499 find_next_bit:
501 /* Are we done? */
502 if (idx >= board_page_len) {
503 goto success;
506 /* Where does ${FOO} appear next? */
507 nret = pcre2_match(variables, (PCRE2_SPTR) board_page,
508 board_page_len, idx, 0, match_data, 0);
510 if (nret == PCRE2_ERROR_NOMATCH) {
511 CHECK_FPRINTF(f, "%s", board_page + idx);
512 goto success;
515 if (nret < 0) {
516 PCRE2_UCHAR8 err_buf[120];
518 pcre2_get_error_message(nret, err_buf, 120);
519 ERROR_MESSAGE(
520 "pcre2_match: error while matching \"%.*s\": %s"
521 " (PCRE2 %d)", (int) (board_page_len - idx),
522 board_page + idx, err_buf, nret);
523 goto done;
526 /* What is FOO? */
527 pcre2_substring_free(var_name);
528 var_name = 0;
529 var_name_len = 0;
531 if ((nret = pcre2_substring_get_byname(match_data,
532 (PCRE2_SPTR) "var",
533 &var_name,
534 &var_name_len))) {
535 PCRE2_UCHAR8 err_buf[120];
537 pcre2_get_error_message(nret, err_buf, 120);
538 ERROR_MESSAGE(
539 "pcre2_substring_get_byname: %s (PCRE2 %d)",
540 err_buf,
541 nret);
542 goto done;
545 match_pos = pcre2_get_ovector_pointer(match_data)[0];
546 after_match_pos = pcre2_get_ovector_pointer(match_data)[1];
547 CHECK_FPRINTF(f, "%.*s", (int) (match_pos - idx), board_page +
548 idx);
549 idx = match_pos;
551 if (!strncasecmp((const char *) var_name, "BOARD",
552 var_name_len)) {
553 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].name);
554 } else if (!strncasecmp((const char *) var_name, "BOARD_TITLE",
555 var_name_len)) {
556 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].title);
557 } else if (!strncasecmp((const char *) var_name, "CHALLENGE",
558 var_name_len)) {
559 CHECK_FPRINTF(f, "%s",
560 conf->challenges[challenge_id].question);
561 } else if (!strncasecmp((const char *) var_name, "CHALLENGE_ID",
562 var_name_len)) {
563 CHECK_FPRINTF(f, "%zu", challenge_id);
564 } else if (!strncasecmp((const char *) var_name, "PAGELINKS",
565 var_name_len)) {
566 CHECK_FPRINTF(f, "[<a href=\"/%s\">0</a>] ",
567 conf->boards[board_idx].name);
569 for (size_t j = 1; j < board_pages_num; ++j) {
570 CHECK_FPRINTF(f,
571 "[<a href=\"/%s/%zu\">%zu</a>] ",
572 conf->boards[board_idx].name, j,
575 } else if (!strncasecmp((const char *) var_name,
576 "RANDOM_HEADER", var_name_len)) {
577 CHECK_FPRINTF(f, "%s", conf->headers[rand() %
578 conf->headers_num]);
579 } else if (!strncasecmp((const char *) var_name, "THREADS",
580 var_name_len)) {
582 * We have to transfer control to db-ZZZ
583 * for a bit - it comes back through
584 * wt_write_post().
586 db_writeback_thread_summaries(board_idx, thread_ids +
587 first_thread_idx,
588 num_threads_this_page, f);
589 } else {
590 CHECK_FPRINTF(f, "${%.*s}", (int) var_name_len,
591 var_name);
594 idx = after_match_pos;
595 goto find_next_bit;
596 success:
600 ret = 0;
601 done:
602 pcre2_match_data_free(match_data);
603 pcre2_substring_free(var_name);
605 if (f) {
606 fclose(f);
609 free(path);
611 return ret;
615 * Write p out to f as the first post of a thread.
617 * This function is intended to be called by
618 * db_writeback_posts_in_thread(), which is called from wt_write_thread().
620 * Preconditions:
622 * - setup_write_thread() has been called more recently than
623 * clean_write_thread().
625 * - p is a fully filled stored_post (i.e. from a row in the database).
627 * - f is an open, writable FILE *.
629 * - board_idx represents a baord.
631 * - If is_summary is not zero, then board_name is a string like
632 * "m" or "sf". Furthermore, if is_op is also not zero, then
633 * total_posts_in_thread is a number suitable for describing the
634 * thread.
636 * Postconditions (success):
638 * - the contents of p have been written to f, which returned no
639 * errors. The format is not precisely defined.
641 int wt_write_post(struct prepared_post *p, FILE *f, uint_fast8_t is_op,
642 uint_fast8_t is_summary, const char *board_name, uintmax_t
643 total_posts_in_thread, uint_fast8_t hr_before)
645 int ret = -1;
646 char *human_readable_time = util_iso8601_from_time_t(p->now);
648 if (!human_readable_time) {
649 ERROR_MESSAGE("util_iso_8601_from_time(%ju) failed",
650 (uintmax_t) p->now);
653 if (hr_before) {
654 /* XXX: make sure this looks decent, both in ff and elinks */
655 CHECK_FPRINTF(f, "<hr />");
658 if (is_op) {
659 CHECK_FPRINTF(f, "<div class=\"op-post\" id=\"post%ju\">",
660 p->id);
661 } else {
662 CHECK_FPRINTF(f, "<div class=\"reply-post\" id=\"post%ju\">",
663 p->id);
666 CHECK_FPRINTF(f, "<table><tr>");
667 CHECK_FPRINTF(f, "<td colspan=\"2\">");
668 CHECK_FPRINTF(f, "<div class=\"post-info\">");
670 if (p->subject_len) {
671 CHECK_FPRINTF(f, "<span class=\"post-subject\">%.*s</span> ",
672 (int) p->subject_len, p->subject);
675 if (p->name_len) {
676 if (p->email_len) {
677 CHECK_FPRINTF(f, "<a href=\"mailto:%.*s\">",
678 (int) p->email_len, p->email);
681 CHECK_FPRINTF(f, "<span class=\"post-author\">%.*s</span>",
682 (int) p->name_len, p->name);
684 if (p->email_len) {
685 CHECK_FPRINTF(f, "</a>");
688 if (p->tripcode_len) {
689 CHECK_FPRINTF(f, "<span class=\"post-tripcode\">");
690 CHECK_FPRINTF(f, "!%.*s</span>", (int) p->tripcode_len,
691 p->tripcode);
695 CHECK_FPRINTF(f, " <span class=\"post-time\">%s</span>",
696 human_readable_time ? human_readable_time : "NEVER");
697 CHECK_FPRINTF(f, " <span class=\"post-number\">No. %ju</span>", p->id);
699 if (is_op) {
700 if (p->thread_closed) {
701 CHECK_FPRINTF(f, " <span class=\"thread-closed\">");
702 CHECK_FPRINTF(f, "Closed</span>");
705 if (p->thread_stickied) {
706 CHECK_FPRINTF(f, " <span class=\"thread-stickied\">");
707 CHECK_FPRINTF(f, "Sticky</span>");
711 if (is_summary &&
712 is_op) {
713 CHECK_FPRINTF(f, " [<a href=\"/%s/res/%ju\">View</a>]",
714 board_name, p->id);
717 CHECK_FPRINTF(f, "</div></td></tr>");
719 if (p->system_full_path_len) {
720 CHECK_FPRINTF(f, "<tr><td colspan=\"2\">");
721 CHECK_FPRINTF(f, "<span class=\"file-info\">");
722 CHECK_FPRINTF(f, "<a class=\"filelink\" href=\"%.*s\">",
723 (int) p->system_full_path_len,
724 p->system_full_path);
725 CHECK_FPRINTF(f, "%.*s", (int) p->file_name_len, p->file_name);
726 CHECK_FPRINTF(f, "</a>");
727 CHECK_FPRINTF(f, " (%.*s)</span></td></tr><tr>",
728 (int) p->file_info_len, p->file_info);
729 CHECK_FPRINTF(f, "<td class=\"thin\">");
730 CHECK_FPRINTF(f, "<a class=\"filelink\" href=\"%.*s\">",
731 (int) p->system_full_path_len,
732 p->system_full_path);
733 CHECK_FPRINTF(f, "<img src=\"%.*s\" alt=\"image\" ",
734 (int) p->system_thumb_path_len,
735 p->system_thumb_path);
736 CHECK_FPRINTF(f, "class=\"thumb\"/>");
737 CHECK_FPRINTF(f, "</a></td>");
738 } else {
739 CHECK_FPRINTF(f, "<tr><td class=\"thin\">&nbsp;</td>");
742 CHECK_FPRINTF(f, "<td>");
743 CHECK_FPRINTF(f, "<div class=\"post-comment\">");
744 CHECK_FPRINTF(f, "%.*s", (int) p->comment_len, p->comment);
745 CHECK_FPRINTF(f, "</div>");
746 CHECK_FPRINTF(f, "</td></tr></table></div>");
748 if (is_summary &&
749 is_op) {
750 CHECK_FPRINTF(f, "<div class=\"reply-count-info\">");
751 CHECK_FPRINTF(f, "%ju post%s in thread. ",
752 total_posts_in_thread, (total_posts_in_thread ==
753 1) ? "" : "s");
754 CHECK_FPRINTF(f, "<a href=\"/%s/res/%ju\">View thread</a>",
755 board_name, p->id);
756 CHECK_FPRINTF(f, "</div>");
759 ret = 0;
760 done:
761 free(human_readable_time);
763 return ret;
767 * Write out the HTML file for a thread
769 * Preconditions:
771 * - setup_write_thread() has been called more recently than
772 * clean_write_thread().
774 * - board_idx represents a board, AND THE LOCK IS HELD.
776 * - thread is the post_id of a thread.
778 * Postconditions (success):
780 * - A file at ${static_www_folder}/${board}/res/${thread}/index.html
781 * has been written out, representing the recent state of the
782 * thread.
784 int wt_write_thread(size_t board_idx, uintmax_t thread)
786 int ret = -1;
787 size_t idx = 0;
788 size_t match_pos = 0;
789 size_t after_match_pos = 0;
790 pcre2_match_data *match_data = 0;
791 int nret = 0;
792 FILE *f = 0;
793 size_t len = 0;
794 char *index_path = 0;
795 PCRE2_UCHAR *var_name = 0;
796 PCRE2_SIZE var_name_len = 0;
797 size_t challenge_id = rand() % conf->challenges_num;
799 len = snprintf(0, 0, "%s/%s/res/%ju/index.html",
800 conf->static_www_folder, conf->boards[board_idx].name,
801 thread);
803 if (len + 1 < len) {
804 ERROR_MESSAGE("overflow");
805 goto done;
808 if (!(index_path = malloc(len + 1))) {
809 PERROR_MESSAGE("malloc");
810 goto done;
813 sprintf(index_path, "%s/%s/res/%ju", conf->static_www_folder,
814 conf->boards[board_idx].name, thread);
815 errno = 0;
817 /* Make the directory. We shouldn't need mkdir -p. */
818 if (mkdir(index_path, 0755) < 0) {
819 if (errno != EEXIST) {
820 PERROR_MESSAGE("mkdir");
821 ERROR_MESSAGE("mkdir(\"%s\") failed", index_path);
822 goto done;
826 /* Now open the file */
827 sprintf(index_path, "%s/%s/res/%ju/index.html", conf->static_www_folder,
828 conf->boards[board_idx].name, thread);
830 if (!(f = fopen(index_path, "w"))) {
831 PERROR_MESSAGE("fopen");
832 ERROR_MESSAGE("fopen(\"%s\", \"w\") failed", index_path);
833 goto done;
836 /* 3 capture groups: more than sufficient for ${foo} */
837 if (!(match_data = pcre2_match_data_create(3, 0))) {
838 PERROR_MESSAGE("pcre2_match_data_create");
839 goto done;
842 find_next_bit:
844 /* Are we done? */
845 if (idx >= thread_page_len) {
846 goto success;
849 /* Where does ${FOO} appear next? */
850 nret = pcre2_match(variables, (PCRE2_SPTR) thread_page, thread_page_len,
851 idx, 0, match_data, 0);
853 if (nret == PCRE2_ERROR_NOMATCH) {
854 CHECK_FPRINTF(f, "%s", thread_page + idx);
855 goto success;
858 if (nret < 0) {
859 PCRE2_UCHAR8 err_buf[120];
861 pcre2_get_error_message(nret, err_buf, 120);
862 ERROR_MESSAGE("pcre2_match: error while matching \"%.*s\": %s"
863 " (PCRE2 %d)", (int) (thread_page_len - idx),
864 thread_page + idx,
865 err_buf, nret);
866 goto done;
869 /* What is FOO? */
870 pcre2_substring_free(var_name);
871 var_name = 0;
872 var_name_len = 0;
874 if ((nret = pcre2_substring_get_byname(match_data, (PCRE2_SPTR) "var",
875 &var_name, &var_name_len))) {
876 PCRE2_UCHAR8 err_buf[120];
878 pcre2_get_error_message(nret, err_buf, 120);
879 ERROR_MESSAGE("pcre2_substring_get_byname: %s (PCRE2 %d)",
880 err_buf, nret);
881 goto done;
884 match_pos = pcre2_get_ovector_pointer(match_data)[0];
885 after_match_pos = pcre2_get_ovector_pointer(match_data)[1];
886 CHECK_FPRINTF(f, "%.*s", (int) (match_pos - idx), thread_page + idx);
887 idx = match_pos;
889 if (!strncasecmp((const char *) var_name, "BOARD", var_name_len)) {
890 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].name);
891 } else if (!strncasecmp((const char *) var_name, "BOARD_TITLE",
892 var_name_len)) {
893 CHECK_FPRINTF(f, "%s", conf->boards[board_idx].title);
894 } else if (!strncasecmp((const char *) var_name, "CHALLENGE",
895 var_name_len)) {
896 CHECK_FPRINTF(f, "%s", conf->challenges[challenge_id].question);
897 } else if (!strncasecmp((const char *) var_name, "CHALLENGE_ID",
898 var_name_len)) {
899 CHECK_FPRINTF(f, "%zu", challenge_id);
900 } else if (!strncasecmp((const char *) var_name, "OP_SUBJECT",
901 var_name_len)) {
902 char *subject = 0;
903 size_t subject_len = 0;
905 if (db_extract_subject(board_idx, thread, &subject,
906 &subject_len) < 0) {
907 goto done;
910 if (subject) {
911 CHECK_FPRINTF(f, "%.*s", (int) subject_len, subject);
914 free(subject);
915 } else if (!strncasecmp((const char *) var_name, "RANDOM_HEADER",
916 var_name_len)) {
917 CHECK_FPRINTF(f, "%s", conf->headers[rand() %
918 conf->headers_num]);
919 } else if (!strncasecmp((const char *) var_name, "THREAD_NUMBER",
920 var_name_len)) {
921 CHECK_FPRINTF(f, "%zu", thread);
922 } else if (!strncasecmp((const char *) var_name, "POSTS",
923 var_name_len)) {
925 * We have to transfer control to db-ZZZ for a bit
926 * - it comes back through wt_write_post().
928 db_writeback_posts_in_thread(board_idx, thread, f,
929 wt_write_post);
930 } else {
931 CHECK_FPRINTF(f, "${%.*s}", (int) var_name_len, var_name);
934 idx = after_match_pos;
935 goto find_next_bit;
936 success:
937 ret = 0;
938 done:
939 pcre2_match_data_free(match_data);
940 pcre2_substring_free(var_name);
942 if (f) {
943 fclose(f);
946 free(index_path);
948 return ret;
952 * Clean any memory from this file
954 * Postconditions (success):
956 * - Valgrind won't report any memory leaks from this file.
958 * - setup_write_thread() can be safely called again.
960 void clean_write_thread(void)
962 conf = 0;
963 free(board_page);
964 free(thread_page);
965 board_page = 0;
966 board_page_len = 0;
967 thread_page = 0;
968 thread_page_len = 0;
969 pcre2_code_free(variables);
970 variables = 0;