mhd_typst.c (24419B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file mhd_typst.c 18 * @brief MHD utility functions for PDF generation 19 * @author Christian Grothoff 20 * 21 * 22 */ 23 #include "platform.h" /* UNNECESSARY? */ 24 #include "taler/taler_util.h" 25 #include "taler/taler_mhd_lib.h" 26 #include <microhttpd.h> 27 28 29 /** 30 * Information about a specific typst invocation. 31 */ 32 struct TypstStage 33 { 34 /** 35 * Name of the FIFO for the typst output. 36 */ 37 char *filename; 38 39 /** 40 * Typst context we are part of. 41 */ 42 struct TALER_MHD_TypstContext *tc; 43 44 /** 45 * Handle to the typst process. 46 */ 47 struct GNUNET_Process *proc; 48 49 /** 50 * Handle to be notified about stage completion. 51 */ 52 struct GNUNET_ChildWaitHandle *cwh; 53 54 }; 55 56 57 struct TALER_MHD_TypstContext 58 { 59 60 /** 61 * Project data to use for Typst. 62 */ 63 const struct GNUNET_OS_ProjectData *pd; 64 65 /** 66 * Directory where we create temporary files (or FIFOs) for the IPC. 67 */ 68 char *tmpdir; 69 70 /** 71 * Array of stages producing PDFs to be combined. 72 */ 73 struct TypstStage *stages; 74 75 /** 76 * Handle for pdftk combining the various PDFs. 77 */ 78 struct GNUNET_Process *proc; 79 80 /** 81 * Handle to wait for @e proc to complete. 82 */ 83 struct GNUNET_ChildWaitHandle *cwh; 84 85 /** 86 * Callback to call on the final result. 87 */ 88 TALER_MHD_TypstResultCallback cb; 89 90 /** 91 * Closure for @e cb 92 */ 93 void *cb_cls; 94 95 /** 96 * Task for async work. 97 */ 98 struct GNUNET_SCHEDULER_Task *t; 99 100 /** 101 * Name of the final file created by pdftk. 102 */ 103 char *output_file; 104 105 /** 106 * Context for fail_async_cb(). 107 */ 108 char *async_hint; 109 110 /** 111 * Context for fail_async_cb(). 112 */ 113 enum TALER_ErrorCode async_ec; 114 115 /** 116 * Length of the @e stages array. 117 */ 118 unsigned int num_stages; 119 120 /** 121 * Number of still active stages. 122 */ 123 unsigned int active_stages; 124 125 /** 126 * Should the directory be removed when done? 127 */ 128 bool remove_on_exit; 129 }; 130 131 132 void 133 TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc) 134 { 135 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 136 "Cleaning up TypstContext\n"); 137 if (NULL != tc->t) 138 { 139 GNUNET_SCHEDULER_cancel (tc->t); 140 tc->t = NULL; 141 } 142 for (unsigned int i = 0; i<tc->num_stages; i++) 143 { 144 struct TypstStage *stage = &tc->stages[i]; 145 146 if (NULL != stage->cwh) 147 { 148 GNUNET_wait_child_cancel (stage->cwh); 149 stage->cwh = NULL; 150 } 151 if (NULL != stage->proc) 152 { 153 GNUNET_break (GNUNET_OK == 154 GNUNET_process_kill (stage->proc, 155 SIGKILL)); 156 GNUNET_process_destroy (stage->proc); 157 stage->proc = NULL; 158 } 159 GNUNET_free (stage->filename); 160 } 161 GNUNET_free (tc->stages); 162 if (NULL != tc->cwh) 163 { 164 GNUNET_wait_child_cancel (tc->cwh); 165 tc->cwh = NULL; 166 } 167 if (NULL != tc->proc) 168 { 169 GNUNET_break (GNUNET_OK == 170 GNUNET_process_kill (tc->proc, 171 SIGKILL)); 172 GNUNET_process_destroy (tc->proc); 173 } 174 GNUNET_free (tc->output_file); 175 if (NULL != tc->tmpdir) 176 { 177 if (tc->remove_on_exit) 178 GNUNET_DISK_directory_remove (tc->tmpdir); 179 GNUNET_free (tc->tmpdir); 180 } 181 GNUNET_free (tc); 182 } 183 184 185 /** 186 * Create file in @a tmpdir with one of the PDF inputs. 187 * 188 * @param[out] stage initialized stage data 189 * @param tmpdir where to place temporary files 190 * @param data input JSON with PDF data 191 * @return true on success 192 */ 193 static bool 194 inline_pdf_stage (struct TypstStage *stage, 195 const char *tmpdir, 196 const json_t *data) 197 { 198 const char *str = json_string_value (data); 199 char *fn; 200 size_t n; 201 void *b; 202 int fd; 203 204 if (NULL == str) 205 { 206 GNUNET_break (0); 207 return false; 208 } 209 b = NULL; 210 n = GNUNET_STRINGS_base64_decode (str, 211 strlen (str), 212 &b); 213 if (NULL == b) 214 { 215 GNUNET_break (0); 216 return false; 217 } 218 GNUNET_asprintf (&fn, 219 "%s/external-", 220 tmpdir); 221 stage->filename = GNUNET_DISK_mktemp (fn); 222 if (NULL == stage->filename) 223 { 224 GNUNET_break (0); 225 GNUNET_free (b); 226 GNUNET_free (fn); 227 return false; 228 } 229 GNUNET_free (fn); 230 fd = open (stage->filename, 231 O_WRONLY | O_TRUNC, 232 S_IRUSR | S_IWUSR); 233 if (-1 == fd) 234 { 235 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 236 "open", 237 stage->filename); 238 if (0 != unlink (stage->filename)) 239 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 240 "unlink", 241 stage->filename); 242 GNUNET_free (b); 243 GNUNET_free (stage->filename); 244 return false; 245 } 246 247 { 248 size_t off = 0; 249 250 while (off < n) 251 { 252 ssize_t r; 253 254 r = write (fd, 255 b + off, 256 n - off); 257 if (-1 == r) 258 { 259 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 260 "write", 261 stage->filename); 262 GNUNET_break (0 == close (fd)); 263 GNUNET_free (b); 264 GNUNET_free (stage->filename); 265 return false; 266 } 267 off += r; 268 } 269 } 270 GNUNET_break (0 == close (fd)); 271 GNUNET_free (b); 272 return true; 273 } 274 275 276 /** 277 * Generate a response for @a tc indicating an error of type @a ec. 278 * 279 * @param[in,out] tc context to fail 280 * @param ec error code to return 281 * @param hint hint text to return 282 */ 283 static void 284 typst_context_fail (struct TALER_MHD_TypstContext *tc, 285 enum TALER_ErrorCode ec, 286 const char *hint) 287 { 288 struct TALER_MHD_TypstResponse resp = { 289 .ec = ec, 290 .details.hint = hint 291 }; 292 293 if (NULL != tc->cb) 294 { 295 tc->cb (tc->cb_cls, 296 &resp); 297 tc->cb = NULL; 298 } 299 } 300 301 302 /** 303 * Helper task for typst_context_fail_async(). 304 * 305 * @param cls a `struct TALER_MHD_TypstContext` 306 */ 307 static void 308 fail_async_cb (void *cls) 309 { 310 struct TALER_MHD_TypstContext *tc = cls; 311 312 tc->t = NULL; 313 typst_context_fail (tc, 314 tc->async_ec, 315 tc->async_hint); 316 GNUNET_free (tc->async_hint); 317 TALER_MHD_typst_cancel (tc); 318 } 319 320 321 /** 322 * Generate a response for @a tc indicating an error of type @a ec. 323 * 324 * @param[in,out] tc context to fail 325 * @param ec error code to return 326 * @param hint hint text to return 327 */ 328 static void 329 typst_context_fail_async (struct TALER_MHD_TypstContext *tc, 330 enum TALER_ErrorCode ec, 331 const char *hint) 332 { 333 tc->async_ec = ec; 334 tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint); 335 tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb, 336 tc); 337 } 338 339 340 /** 341 * Called when the pdftk helper exited. 342 * 343 * @param cls our `struct TALER_MHD_TypstContext *` 344 * @param type type of the process 345 * @param exit_code status code of the process 346 */ 347 static void 348 pdftk_done_cb (void *cls, 349 enum GNUNET_OS_ProcessStatusType type, 350 long unsigned int exit_code) 351 { 352 struct TALER_MHD_TypstContext *tc = cls; 353 354 tc->cwh = NULL; 355 GNUNET_process_destroy (tc->proc); 356 tc->proc = NULL; 357 switch (type) 358 { 359 case GNUNET_OS_PROCESS_UNKNOWN: 360 GNUNET_assert (0); 361 return; 362 case GNUNET_OS_PROCESS_RUNNING: 363 /* we should not get this notification */ 364 GNUNET_break (0); 365 return; 366 case GNUNET_OS_PROCESS_STOPPED: 367 /* Someone is SIGSTOPing our helper!? */ 368 GNUNET_break (0); 369 return; 370 case GNUNET_OS_PROCESS_EXITED: 371 if (0 != exit_code) 372 { 373 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 374 "pdftk exited with status %d\n", 375 (int) exit_code); 376 typst_context_fail (tc, 377 TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE, 378 "pdftk failed"); 379 } 380 else 381 { 382 struct TALER_MHD_TypstResponse resp = { 383 .ec = TALER_EC_NONE, 384 .details.filename = tc->output_file, 385 }; 386 387 GNUNET_assert (NULL != tc->cb); 388 tc->cb (tc->cb_cls, 389 &resp); 390 tc->cb = NULL; 391 } 392 break; 393 case GNUNET_OS_PROCESS_SIGNALED: 394 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 395 "pdftk died with signal %d\n", 396 (int) exit_code); 397 typst_context_fail (tc, 398 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 399 "pdftk killed by signal"); 400 break; 401 } 402 TALER_MHD_typst_cancel (tc); 403 } 404 405 406 /** 407 * Function called once all of the individual stages are done. 408 * Triggers the pdftk run for @a tc. 409 * 410 * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for 411 */ 412 static void 413 complete_response (void *cls) 414 { 415 struct TALER_MHD_TypstContext *tc = cls; 416 const char *argv[tc->num_stages + 5]; 417 418 tc->t = NULL; 419 argv[0] = "pdftk"; 420 for (unsigned int i = 0; i<tc->num_stages; i++) 421 argv[i + 1] = tc->stages[i].filename; 422 argv[tc->num_stages + 1] = "cat"; 423 argv[tc->num_stages + 2] = "output"; 424 argv[tc->num_stages + 3] = tc->output_file; 425 argv[tc->num_stages + 4] = NULL; 426 tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 427 if (GNUNET_OK != 428 GNUNET_process_run_command_argv (tc->proc, 429 argv[0], 430 argv)) 431 { 432 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 433 "fork"); 434 GNUNET_process_destroy (tc->proc); 435 tc->proc = NULL; 436 TALER_MHD_typst_cancel (tc); 437 return; 438 } 439 tc->cwh = GNUNET_wait_child (tc->proc, 440 &pdftk_done_cb, 441 tc); 442 GNUNET_assert (NULL != tc->cwh); 443 } 444 445 446 /** 447 * Cancel typst. Wrapper task to do so asynchronously. 448 * 449 * @param[in] cls a `struct TALER_MHD_TypstContext` 450 */ 451 static void 452 cancel_async (void *cls) 453 { 454 struct TALER_MHD_TypstContext *tc = cls; 455 456 tc->t = NULL; 457 TALER_MHD_typst_cancel (tc); 458 } 459 460 461 /** 462 * Called when a typst helper exited. 463 * 464 * @param cls our `struct TypstStage *` 465 * @param type type of the process 466 * @param exit_code status code of the process 467 */ 468 static void 469 typst_done_cb (void *cls, 470 enum GNUNET_OS_ProcessStatusType type, 471 long unsigned int exit_code) 472 { 473 struct TypstStage *stage = cls; 474 struct TALER_MHD_TypstContext *tc = stage->tc; 475 476 stage->cwh = NULL; 477 GNUNET_process_destroy (stage->proc); 478 stage->proc = NULL; 479 switch (type) 480 { 481 case GNUNET_OS_PROCESS_UNKNOWN: 482 GNUNET_assert (0); 483 return; 484 case GNUNET_OS_PROCESS_RUNNING: 485 /* we should not get this notification */ 486 GNUNET_break (0); 487 return; 488 case GNUNET_OS_PROCESS_STOPPED: 489 /* Someone is SIGSTOPing our helper!? */ 490 GNUNET_break (0); 491 return; 492 case GNUNET_OS_PROCESS_EXITED: 493 if (0 != exit_code) 494 { 495 char err[128]; 496 497 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 498 "typst exited with status %d\n", 499 (int) exit_code); 500 GNUNET_snprintf (err, 501 sizeof (err), 502 "Typst exited with status %d", 503 (int) exit_code); 504 typst_context_fail (tc, 505 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 506 err); 507 GNUNET_assert (NULL == tc->t); 508 tc->t = GNUNET_SCHEDULER_add_now (&cancel_async, 509 tc); 510 return; 511 } 512 break; 513 case GNUNET_OS_PROCESS_SIGNALED: 514 { 515 char err[128]; 516 517 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 518 "typst died with signal %d\n", 519 (int) exit_code); 520 GNUNET_snprintf (err, 521 sizeof (err), 522 "Typst died with signal %d", 523 (int) exit_code); 524 typst_context_fail (tc, 525 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 526 err); 527 GNUNET_assert (NULL == tc->t); 528 tc->t = GNUNET_SCHEDULER_add_now (&cancel_async, 529 tc); 530 return; 531 } 532 break; 533 } 534 tc->active_stages--; 535 if (NULL != stage->proc) 536 { 537 GNUNET_process_destroy (stage->proc); 538 stage->proc = NULL; 539 } 540 if (0 != tc->active_stages) 541 return; 542 GNUNET_assert (NULL == tc->t); 543 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 544 tc); 545 } 546 547 548 /** 549 * Check that @a id only consists of characters that are safe to 550 * interpolate into a Typst `#import` statement (alphanumerics and 551 * `.`, `-`, `_`). This prevents code/path injection via the form 552 * name or version. 553 * 554 * @param id identifier to validate 555 * @return true if @a id is non-empty and only uses the safe charset 556 */ 557 static bool 558 is_valid_form_identifier (const char *id) 559 { 560 if ( (NULL == id) || 561 ('\0' == id[0]) ) 562 return false; 563 for (const char *p = id; '\0' != *p; p++) 564 { 565 char c = *p; 566 567 if (! ( ( ('a' <= c) && ('z' >= c)) || 568 ( ('A' <= c) && ('Z' >= c)) || 569 ( ('0' <= c) && ('9' >= c)) || 570 ('.' == c) || 571 ('-' == c) || 572 ('_' == c) ) ) 573 return false; 574 } 575 return true; 576 } 577 578 579 /** 580 * Setup typst stage to produce one of the PDF inputs. 581 * 582 * @param[out] stage initialized stage data 583 * @param i index of the stage 584 * @param tmpdir where to place temporary files 585 * @param template_path where to find templates 586 * @param doc input document specification 587 * @return true on success 588 */ 589 static bool 590 setup_stage (struct TypstStage *stage, 591 unsigned int i, 592 const char *tmpdir, 593 const char *template_path, 594 const struct TALER_MHD_TypstDocument *doc) 595 { 596 struct TALER_MHD_TypstContext *tc = stage->tc; 597 char *input; 598 bool is_dot_typ; 599 600 if (NULL == doc->form_name) 601 { 602 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 603 "Stage %u: Dumping inlined PDF attachment\n", 604 i); 605 return inline_pdf_stage (stage, 606 tmpdir, 607 doc->data); 608 } 609 610 if (! is_valid_form_identifier (doc->form_name)) 611 { 612 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 613 "Stage %u: Invalid form name `%s'\n", 614 i, 615 doc->form_name); 616 return false; 617 } 618 if ( (NULL != doc->form_version) && 619 (! is_valid_form_identifier (doc->form_version)) ) 620 { 621 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 622 "Stage %u: Invalid form version `%s'\n", 623 i, 624 doc->form_version); 625 return false; 626 } 627 628 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 629 "Stage %u: Handling form %s\n", 630 i, 631 doc->form_name); 632 633 /* Setup inputs */ 634 { 635 char *dirname; 636 637 GNUNET_asprintf (&dirname, 638 "%s/%u/", 639 tmpdir, 640 i); 641 if (GNUNET_OK != 642 GNUNET_DISK_directory_create (dirname)) 643 { 644 GNUNET_free (dirname); 645 return false; 646 } 647 GNUNET_free (dirname); 648 } 649 650 /* Setup data input */ 651 { 652 char *jfn; 653 654 GNUNET_asprintf (&jfn, 655 "%s/%u/input.json", 656 tmpdir, 657 i); 658 if (0 != 659 json_dump_file (doc->data, 660 jfn, 661 JSON_INDENT (2))) 662 { 663 GNUNET_break (0); 664 GNUNET_free (jfn); 665 return false; 666 } 667 GNUNET_free (jfn); 668 } 669 670 /* setup output file name */ 671 GNUNET_asprintf (&stage->filename, 672 "%s/%u/input.pdf", 673 tmpdir, 674 i); 675 676 /* setup main input Typst file */ 677 { 678 char *intyp; 679 size_t slen = strlen (doc->form_name); 680 681 is_dot_typ = ( (slen > 4) && 682 (0 == memcmp (&doc->form_name[slen - 4], 683 ".typ", 684 4)) ); 685 /* We do not append the ":$VERSION" if a filename ending with ".typ" 686 is given. Otherwise we append the version, or ":0.0.0" if no 687 explicit version is given. */ 688 GNUNET_asprintf (&intyp, 689 "#import \"%s/%s%s%s\": form\n" 690 "#form(json(\"input.json\"))\n", 691 template_path, 692 doc->form_name, 693 is_dot_typ ? "" : ":", 694 is_dot_typ 695 ? "" 696 : ( (NULL == doc->form_version) 697 ? "0.0.0" 698 : doc->form_version)); 699 GNUNET_asprintf (&input, 700 "%s/%u/input.typ", 701 tmpdir, 702 i); 703 if (GNUNET_OK != 704 GNUNET_DISK_fn_write (input, 705 intyp, 706 strlen (intyp), 707 GNUNET_DISK_PERM_USER_READ)) 708 { 709 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 710 "write", 711 input); 712 GNUNET_free (input); 713 GNUNET_free (intyp); 714 return false; 715 } 716 GNUNET_free (intyp); 717 } 718 719 /* now setup typst invocation */ 720 { 721 const char *argv[6]; 722 723 if (is_dot_typ) 724 { 725 /* This deliberately breaks the typst sandbox. Why? Because Typst sucks 726 and does not support multiple roots, but here we have dynamic data in 727 /tmp and a style file outside of /tmp (and copying is also not 728 practical as we don't know what all to copy). Typst should really 729 support multiple roots. Anyway, in production this path should not 730 happen, because there we use Typst packages. */ 731 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 732 "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n", 733 doc->form_name); 734 argv[0] = "typst"; 735 argv[1] = "compile"; 736 argv[2] = "--root"; 737 argv[3] = "/"; 738 argv[4] = input; 739 argv[5] = NULL; 740 } 741 else 742 { 743 argv[0] = "typst"; 744 argv[1] = "compile"; 745 argv[2] = input; 746 argv[3] = NULL; 747 } 748 stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 749 { 750 char *datadir; 751 752 datadir = GNUNET_OS_installation_get_path ( 753 tc->pd, 754 GNUNET_OS_IPK_DATADIR); 755 GNUNET_assert (GNUNET_OK == 756 GNUNET_process_set_options ( 757 stage->proc, 758 GNUNET_process_option_set_environment ("XDG_DATA_HOME", 759 datadir))); 760 GNUNET_free (datadir); 761 } 762 if (GNUNET_OK != 763 GNUNET_process_run_command_argv (stage->proc, 764 "typst", 765 argv)) 766 { 767 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 768 "fork"); 769 GNUNET_free (input); 770 GNUNET_process_destroy (stage->proc); 771 stage->proc = NULL; 772 return false; 773 } 774 GNUNET_free (input); 775 tc->active_stages++; 776 stage->cwh = GNUNET_wait_child (stage->proc, 777 &typst_done_cb, 778 stage); 779 GNUNET_assert (NULL != stage->cwh); 780 } 781 return true; 782 } 783 784 785 struct TALER_MHD_TypstContext * 786 TALER_MHD_typst ( 787 const struct GNUNET_OS_ProjectData *pd, 788 const struct GNUNET_CONFIGURATION_Handle *cfg, 789 bool remove_on_exit, 790 const char *cfg_section_name, 791 unsigned int num_documents, 792 const struct TALER_MHD_TypstDocument docs[static num_documents], 793 TALER_MHD_TypstResultCallback cb, 794 void *cb_cls) 795 { 796 static enum GNUNET_GenericReturnValue once = GNUNET_NO; 797 struct TALER_MHD_TypstContext *tc; 798 799 switch (once) 800 { 801 case GNUNET_OK: 802 break; 803 case GNUNET_NO: 804 if (GNUNET_SYSERR == 805 GNUNET_OS_check_helper_binary ("typst", 806 false, 807 NULL)) 808 { 809 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 810 "`typst' command not found\n"); 811 once = GNUNET_SYSERR; 812 return NULL; 813 } 814 if (GNUNET_SYSERR == 815 GNUNET_OS_check_helper_binary ("pdftk", 816 false, 817 NULL)) 818 { 819 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 820 "`pdftk' command not found\n"); 821 once = GNUNET_SYSERR; 822 return NULL; 823 } 824 once = GNUNET_OK; 825 break; 826 case GNUNET_SYSERR: 827 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 828 "PDF generation initialization failed before, not even trying again\n"); 829 return NULL; 830 } 831 tc = GNUNET_new (struct TALER_MHD_TypstContext); 832 tc->pd = pd; 833 tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX"); 834 tc->remove_on_exit = remove_on_exit; 835 tc->cb = cb; 836 tc->cb_cls = cb_cls; 837 if (NULL == mkdtemp (tc->tmpdir)) 838 { 839 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 840 "mkdtemp", 841 tc->tmpdir); 842 GNUNET_free (tc->tmpdir); 843 TALER_MHD_typst_cancel (tc); 844 return NULL; 845 } 846 GNUNET_asprintf (&tc->output_file, 847 "%s/final.pdf", 848 tc->tmpdir); 849 850 /* setup typst stages */ 851 { 852 char *template_path; 853 854 if (GNUNET_OK != 855 GNUNET_CONFIGURATION_get_value_string (cfg, 856 cfg_section_name, 857 "TYPST_TEMPLATES", 858 &template_path)) 859 { 860 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 861 cfg_section_name, 862 "TYPST_TEMPLATES"); 863 TALER_MHD_typst_cancel (tc); 864 return NULL; 865 } 866 if ('@' != template_path[0]) 867 template_path = GNUNET_CONFIGURATION_expand_dollar (cfg, 868 template_path); 869 tc->stages = GNUNET_new_array (num_documents, 870 struct TypstStage); 871 tc->num_stages = num_documents; 872 for (unsigned int i = 0; i<num_documents; i++) 873 { 874 tc->stages[i].tc = tc; 875 if (! setup_stage (&tc->stages[i], 876 i, 877 tc->tmpdir, 878 template_path, 879 &docs[i])) 880 { 881 char err[128]; 882 883 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 884 "Typst setup failed on stage %u\n", 885 i); 886 GNUNET_snprintf (err, 887 sizeof (err), 888 "Typst setup failed on stage %u", 889 i); 890 typst_context_fail_async (tc, 891 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 892 err); 893 return tc; 894 } 895 } 896 GNUNET_free (template_path); 897 } 898 if (0 == tc->active_stages) 899 { 900 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 901 tc); 902 } 903 return tc; 904 } 905 906 907 struct MHD_Response * 908 TALER_MHD_response_from_pdf_file (const char *filename) 909 { 910 struct MHD_Response *resp; 911 struct stat s; 912 int fd; 913 914 fd = open (filename, 915 O_RDONLY); 916 if (-1 == fd) 917 { 918 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 919 "open", 920 filename); 921 return NULL; 922 } 923 if (0 != 924 fstat (fd, 925 &s)) 926 { 927 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 928 "fstat", 929 filename); 930 GNUNET_assert (0 == close (fd)); 931 return NULL; 932 } 933 resp = MHD_create_response_from_fd (s.st_size, 934 fd); 935 TALER_MHD_add_global_headers (resp, 936 false); 937 GNUNET_break (MHD_YES == 938 MHD_add_response_header (resp, 939 MHD_HTTP_HEADER_CONTENT_TYPE, 940 "application/pdf")); 941 return resp; 942 }