mhd_typst.c (25644B)
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 typst_context_fail (tc, 366 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 367 "pdftk in unexpected state"); 368 break; 369 case GNUNET_OS_PROCESS_STOPPED: 370 /* Someone is SIGSTOPing our helper!? */ 371 GNUNET_break (0); 372 typst_context_fail (tc, 373 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 374 "pdftk was stopped"); 375 break; 376 case GNUNET_OS_PROCESS_EXITED: 377 if (0 != exit_code) 378 { 379 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 380 "pdftk exited with status %d\n", 381 (int) exit_code); 382 typst_context_fail (tc, 383 TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE, 384 "pdftk failed"); 385 } 386 else 387 { 388 struct TALER_MHD_TypstResponse resp = { 389 .ec = TALER_EC_NONE, 390 .details.filename = tc->output_file, 391 }; 392 393 GNUNET_assert (NULL != tc->cb); 394 tc->cb (tc->cb_cls, 395 &resp); 396 tc->cb = NULL; 397 } 398 break; 399 case GNUNET_OS_PROCESS_SIGNALED: 400 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 401 "pdftk died with signal %d\n", 402 (int) exit_code); 403 typst_context_fail (tc, 404 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 405 "pdftk killed by signal"); 406 break; 407 } 408 TALER_MHD_typst_cancel (tc); 409 } 410 411 412 /** 413 * Function called once all of the individual stages are done. 414 * Triggers the pdftk run for @a tc. 415 * 416 * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for 417 */ 418 static void 419 complete_response (void *cls) 420 { 421 struct TALER_MHD_TypstContext *tc = cls; 422 const char *argv[tc->num_stages + 5]; 423 424 tc->t = NULL; 425 argv[0] = "pdftk"; 426 for (unsigned int i = 0; i<tc->num_stages; i++) 427 argv[i + 1] = tc->stages[i].filename; 428 argv[tc->num_stages + 1] = "cat"; 429 argv[tc->num_stages + 2] = "output"; 430 argv[tc->num_stages + 3] = tc->output_file; 431 argv[tc->num_stages + 4] = NULL; 432 tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 433 if (GNUNET_OK != 434 GNUNET_process_run_command_argv (tc->proc, 435 argv[0], 436 argv)) 437 { 438 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 439 "fork"); 440 GNUNET_process_destroy (tc->proc); 441 tc->proc = NULL; 442 TALER_MHD_typst_cancel (tc); 443 return; 444 } 445 tc->cwh = GNUNET_wait_child (tc->proc, 446 &pdftk_done_cb, 447 tc); 448 GNUNET_assert (NULL != tc->cwh); 449 } 450 451 452 /** 453 * Cancel typst. Wrapper task to do so asynchronously. 454 * 455 * @param[in] cls a `struct TALER_MHD_TypstContext` 456 */ 457 static void 458 cancel_async (void *cls) 459 { 460 struct TALER_MHD_TypstContext *tc = cls; 461 462 tc->t = NULL; 463 TALER_MHD_typst_cancel (tc); 464 } 465 466 467 /** 468 * Fail the typst context @a tc and clean it up asynchronously. 469 * 470 * Note that several stages may fail within the same SIGCHLD batch, in 471 * which case the clean-up task will already have been scheduled by the 472 * first failure; we must not schedule it a second time (and must not 473 * clean up synchronously, as we are called from the child-death 474 * notification). 475 * 476 * @param[in,out] tc context to fail 477 * @param ec error code to return to the client 478 * @param hint hint text to return to the client 479 */ 480 static void 481 stage_fail (struct TALER_MHD_TypstContext *tc, 482 enum TALER_ErrorCode ec, 483 const char *hint) 484 { 485 typst_context_fail (tc, 486 ec, 487 hint); 488 if (NULL == tc->t) 489 tc->t = GNUNET_SCHEDULER_add_now (&cancel_async, 490 tc); 491 } 492 493 494 /** 495 * Called when a typst helper exited. 496 * 497 * @param cls our `struct TypstStage *` 498 * @param type type of the process 499 * @param exit_code status code of the process 500 */ 501 static void 502 typst_done_cb (void *cls, 503 enum GNUNET_OS_ProcessStatusType type, 504 long unsigned int exit_code) 505 { 506 struct TypstStage *stage = cls; 507 struct TALER_MHD_TypstContext *tc = stage->tc; 508 509 stage->cwh = NULL; 510 GNUNET_process_destroy (stage->proc); 511 stage->proc = NULL; 512 switch (type) 513 { 514 case GNUNET_OS_PROCESS_UNKNOWN: 515 GNUNET_assert (0); 516 return; 517 case GNUNET_OS_PROCESS_RUNNING: 518 /* we should not get this notification */ 519 GNUNET_break (0); 520 stage_fail (tc, 521 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 522 "Typst in unexpected state"); 523 return; 524 case GNUNET_OS_PROCESS_STOPPED: 525 /* Someone is SIGSTOPing our helper!? */ 526 GNUNET_break (0); 527 stage_fail (tc, 528 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 529 "Typst was stopped"); 530 return; 531 case GNUNET_OS_PROCESS_EXITED: 532 if (0 != exit_code) 533 { 534 char err[128]; 535 536 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 537 "typst exited with status %d\n", 538 (int) exit_code); 539 GNUNET_snprintf (err, 540 sizeof (err), 541 "Typst exited with status %d", 542 (int) exit_code); 543 stage_fail (tc, 544 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 545 err); 546 return; 547 } 548 break; 549 case GNUNET_OS_PROCESS_SIGNALED: 550 { 551 char err[128]; 552 553 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 554 "typst died with signal %d\n", 555 (int) exit_code); 556 GNUNET_snprintf (err, 557 sizeof (err), 558 "Typst died with signal %d", 559 (int) exit_code); 560 stage_fail (tc, 561 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 562 err); 563 return; 564 } 565 break; 566 } 567 tc->active_stages--; 568 if (NULL != stage->proc) 569 { 570 GNUNET_process_destroy (stage->proc); 571 stage->proc = NULL; 572 } 573 if (0 != tc->active_stages) 574 return; 575 if (NULL != tc->t) 576 return; /* clean-up of a failed stage is already scheduled */ 577 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 578 tc); 579 } 580 581 582 /** 583 * Check that @a id only consists of characters that are safe to 584 * interpolate into a Typst `#import` statement (alphanumerics and 585 * `.`, `-`, `_`). This prevents code/path injection via the form 586 * name or version. 587 * 588 * @param id identifier to validate 589 * @return true if @a id is non-empty and only uses the safe charset 590 */ 591 static bool 592 is_valid_form_identifier (const char *id) 593 { 594 if ( (NULL == id) || 595 ('\0' == id[0]) ) 596 return false; 597 for (const char *p = id; '\0' != *p; p++) 598 { 599 char c = *p; 600 601 if (! ( ( ('a' <= c) && ('z' >= c)) || 602 ( ('A' <= c) && ('Z' >= c)) || 603 ( ('0' <= c) && ('9' >= c)) || 604 ('.' == c) || 605 ('-' == c) || 606 ('_' == c) ) ) 607 return false; 608 } 609 return true; 610 } 611 612 613 /** 614 * Setup typst stage to produce one of the PDF inputs. 615 * 616 * @param[out] stage initialized stage data 617 * @param i index of the stage 618 * @param tmpdir where to place temporary files 619 * @param template_path where to find templates 620 * @param doc input document specification 621 * @return true on success 622 */ 623 static bool 624 setup_stage (struct TypstStage *stage, 625 unsigned int i, 626 const char *tmpdir, 627 const char *template_path, 628 const struct TALER_MHD_TypstDocument *doc) 629 { 630 struct TALER_MHD_TypstContext *tc = stage->tc; 631 char *input; 632 bool is_dot_typ; 633 634 if (NULL == doc->form_name) 635 { 636 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 637 "Stage %u: Dumping inlined PDF attachment\n", 638 i); 639 return inline_pdf_stage (stage, 640 tmpdir, 641 doc->data); 642 } 643 644 if (! is_valid_form_identifier (doc->form_name)) 645 { 646 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 647 "Stage %u: Invalid form name `%s'\n", 648 i, 649 doc->form_name); 650 return false; 651 } 652 if ( (NULL != doc->form_version) && 653 (! is_valid_form_identifier (doc->form_version)) ) 654 { 655 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 656 "Stage %u: Invalid form version `%s'\n", 657 i, 658 doc->form_version); 659 return false; 660 } 661 662 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 663 "Stage %u: Handling form %s\n", 664 i, 665 doc->form_name); 666 667 /* Setup inputs */ 668 { 669 char *dirname; 670 671 GNUNET_asprintf (&dirname, 672 "%s/%u/", 673 tmpdir, 674 i); 675 if (GNUNET_OK != 676 GNUNET_DISK_directory_create (dirname)) 677 { 678 GNUNET_free (dirname); 679 return false; 680 } 681 GNUNET_free (dirname); 682 } 683 684 /* Setup data input */ 685 { 686 char *jfn; 687 688 GNUNET_asprintf (&jfn, 689 "%s/%u/input.json", 690 tmpdir, 691 i); 692 if (0 != 693 json_dump_file (doc->data, 694 jfn, 695 JSON_INDENT (2))) 696 { 697 GNUNET_break (0); 698 GNUNET_free (jfn); 699 return false; 700 } 701 GNUNET_free (jfn); 702 } 703 704 /* setup output file name */ 705 GNUNET_asprintf (&stage->filename, 706 "%s/%u/input.pdf", 707 tmpdir, 708 i); 709 710 /* setup main input Typst file */ 711 { 712 char *intyp; 713 size_t slen = strlen (doc->form_name); 714 715 is_dot_typ = ( (slen > 4) && 716 (0 == memcmp (&doc->form_name[slen - 4], 717 ".typ", 718 4)) ); 719 /* We do not append the ":$VERSION" if a filename ending with ".typ" 720 is given. Otherwise we append the version, or ":0.0.0" if no 721 explicit version is given. */ 722 GNUNET_asprintf (&intyp, 723 "#import \"%s/%s%s%s\": form\n" 724 "#form(json(\"input.json\"))\n", 725 template_path, 726 doc->form_name, 727 is_dot_typ ? "" : ":", 728 is_dot_typ 729 ? "" 730 : ( (NULL == doc->form_version) 731 ? "0.0.0" 732 : doc->form_version)); 733 GNUNET_asprintf (&input, 734 "%s/%u/input.typ", 735 tmpdir, 736 i); 737 if (GNUNET_OK != 738 GNUNET_DISK_fn_write (input, 739 intyp, 740 strlen (intyp), 741 GNUNET_DISK_PERM_USER_READ)) 742 { 743 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 744 "write", 745 input); 746 GNUNET_free (input); 747 GNUNET_free (intyp); 748 return false; 749 } 750 GNUNET_free (intyp); 751 } 752 753 /* now setup typst invocation */ 754 { 755 const char *argv[6]; 756 757 if (is_dot_typ) 758 { 759 /* This deliberately breaks the typst sandbox. Why? Because Typst sucks 760 and does not support multiple roots, but here we have dynamic data in 761 /tmp and a style file outside of /tmp (and copying is also not 762 practical as we don't know what all to copy). Typst should really 763 support multiple roots. Anyway, in production this path should not 764 happen, because there we use Typst packages. */ 765 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 766 "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n", 767 doc->form_name); 768 argv[0] = "typst"; 769 argv[1] = "compile"; 770 argv[2] = "--root"; 771 argv[3] = "/"; 772 argv[4] = input; 773 argv[5] = NULL; 774 } 775 else 776 { 777 argv[0] = "typst"; 778 argv[1] = "compile"; 779 argv[2] = input; 780 argv[3] = NULL; 781 } 782 stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 783 { 784 char *datadir; 785 786 datadir = GNUNET_OS_installation_get_path ( 787 tc->pd, 788 GNUNET_OS_IPK_DATADIR); 789 GNUNET_assert (GNUNET_OK == 790 GNUNET_process_set_options ( 791 stage->proc, 792 GNUNET_process_option_set_environment ("XDG_DATA_HOME", 793 datadir))); 794 GNUNET_free (datadir); 795 } 796 if (GNUNET_OK != 797 GNUNET_process_run_command_argv (stage->proc, 798 "typst", 799 argv)) 800 { 801 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 802 "fork"); 803 GNUNET_free (input); 804 GNUNET_process_destroy (stage->proc); 805 stage->proc = NULL; 806 return false; 807 } 808 GNUNET_free (input); 809 tc->active_stages++; 810 stage->cwh = GNUNET_wait_child (stage->proc, 811 &typst_done_cb, 812 stage); 813 GNUNET_assert (NULL != stage->cwh); 814 } 815 return true; 816 } 817 818 819 struct TALER_MHD_TypstContext * 820 TALER_MHD_typst ( 821 const struct GNUNET_OS_ProjectData *pd, 822 const struct GNUNET_CONFIGURATION_Handle *cfg, 823 bool remove_on_exit, 824 const char *cfg_section_name, 825 unsigned int num_documents, 826 const struct TALER_MHD_TypstDocument docs[static num_documents], 827 TALER_MHD_TypstResultCallback cb, 828 void *cb_cls) 829 { 830 static enum GNUNET_GenericReturnValue once = GNUNET_NO; 831 struct TALER_MHD_TypstContext *tc; 832 833 switch (once) 834 { 835 case GNUNET_OK: 836 break; 837 case GNUNET_NO: 838 if (GNUNET_SYSERR == 839 GNUNET_OS_check_helper_binary ("typst", 840 false, 841 NULL)) 842 { 843 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 844 "`typst' command not found\n"); 845 once = GNUNET_SYSERR; 846 return NULL; 847 } 848 if (GNUNET_SYSERR == 849 GNUNET_OS_check_helper_binary ("pdftk", 850 false, 851 NULL)) 852 { 853 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 854 "`pdftk' command not found\n"); 855 once = GNUNET_SYSERR; 856 return NULL; 857 } 858 once = GNUNET_OK; 859 break; 860 case GNUNET_SYSERR: 861 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 862 "PDF generation initialization failed before, not even trying again\n"); 863 return NULL; 864 } 865 tc = GNUNET_new (struct TALER_MHD_TypstContext); 866 tc->pd = pd; 867 tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX"); 868 tc->remove_on_exit = remove_on_exit; 869 tc->cb = cb; 870 tc->cb_cls = cb_cls; 871 if (NULL == mkdtemp (tc->tmpdir)) 872 { 873 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 874 "mkdtemp", 875 tc->tmpdir); 876 GNUNET_free (tc->tmpdir); 877 TALER_MHD_typst_cancel (tc); 878 return NULL; 879 } 880 GNUNET_asprintf (&tc->output_file, 881 "%s/final.pdf", 882 tc->tmpdir); 883 884 /* setup typst stages */ 885 { 886 char *template_path; 887 888 if (GNUNET_OK != 889 GNUNET_CONFIGURATION_get_value_string (cfg, 890 cfg_section_name, 891 "TYPST_TEMPLATES", 892 &template_path)) 893 { 894 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 895 cfg_section_name, 896 "TYPST_TEMPLATES"); 897 TALER_MHD_typst_cancel (tc); 898 return NULL; 899 } 900 if ('@' != template_path[0]) 901 template_path = GNUNET_CONFIGURATION_expand_dollar (cfg, 902 template_path); 903 tc->stages = GNUNET_new_array (num_documents, 904 struct TypstStage); 905 tc->num_stages = num_documents; 906 for (unsigned int i = 0; i<num_documents; i++) 907 { 908 tc->stages[i].tc = tc; 909 if (! setup_stage (&tc->stages[i], 910 i, 911 tc->tmpdir, 912 template_path, 913 &docs[i])) 914 { 915 char err[128]; 916 917 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 918 "Typst setup failed on stage %u\n", 919 i); 920 GNUNET_snprintf (err, 921 sizeof (err), 922 "Typst setup failed on stage %u", 923 i); 924 typst_context_fail_async (tc, 925 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 926 err); 927 GNUNET_free (template_path); 928 return tc; 929 } 930 } 931 GNUNET_free (template_path); 932 } 933 if (0 == tc->active_stages) 934 { 935 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 936 tc); 937 } 938 return tc; 939 } 940 941 942 struct MHD_Response * 943 TALER_MHD_response_from_pdf_file (const char *filename) 944 { 945 struct MHD_Response *resp; 946 struct stat s; 947 int fd; 948 949 fd = open (filename, 950 O_RDONLY); 951 if (-1 == fd) 952 { 953 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 954 "open", 955 filename); 956 return NULL; 957 } 958 if (0 != 959 fstat (fd, 960 &s)) 961 { 962 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 963 "fstat", 964 filename); 965 GNUNET_assert (0 == close (fd)); 966 return NULL; 967 } 968 resp = MHD_create_response_from_fd (s.st_size, 969 fd); 970 if (NULL == resp) 971 { 972 GNUNET_break (0); 973 GNUNET_assert (0 == close (fd)); 974 return NULL; 975 } 976 TALER_MHD_add_global_headers (resp, 977 false); 978 GNUNET_break (MHD_YES == 979 MHD_add_response_header (resp, 980 MHD_HTTP_HEADER_CONTENT_TYPE, 981 "application/pdf")); 982 return resp; 983 }