mhd_typst.c (26424B)
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 /* reap the child, nobody else will (we cancelled the cwh) */ 157 GNUNET_break (GNUNET_OK == 158 GNUNET_process_wait (stage->proc, 159 true, 160 NULL, 161 NULL)); 162 GNUNET_process_destroy (stage->proc); 163 stage->proc = NULL; 164 } 165 GNUNET_free (stage->filename); 166 } 167 GNUNET_free (tc->stages); 168 if (NULL != tc->cwh) 169 { 170 GNUNET_wait_child_cancel (tc->cwh); 171 tc->cwh = NULL; 172 } 173 if (NULL != tc->proc) 174 { 175 GNUNET_break (GNUNET_OK == 176 GNUNET_process_kill (tc->proc, 177 SIGKILL)); 178 GNUNET_break (GNUNET_OK == 179 GNUNET_process_wait (tc->proc, 180 true, 181 NULL, 182 NULL)); 183 GNUNET_process_destroy (tc->proc); 184 } 185 GNUNET_free (tc->output_file); 186 if (NULL != tc->tmpdir) 187 { 188 if (tc->remove_on_exit) 189 GNUNET_DISK_directory_remove (tc->tmpdir); 190 GNUNET_free (tc->tmpdir); 191 } 192 GNUNET_free (tc); 193 } 194 195 196 /** 197 * Create file in @a tmpdir with one of the PDF inputs. 198 * 199 * @param[out] stage initialized stage data 200 * @param tmpdir where to place temporary files 201 * @param data input JSON with PDF data 202 * @return true on success 203 */ 204 static bool 205 inline_pdf_stage (struct TypstStage *stage, 206 const char *tmpdir, 207 const json_t *data) 208 { 209 const char *str = json_string_value (data); 210 char *fn; 211 size_t n; 212 void *b; 213 int fd; 214 215 if (NULL == str) 216 { 217 GNUNET_break (0); 218 return false; 219 } 220 b = NULL; 221 n = GNUNET_STRINGS_base64_decode (str, 222 strlen (str), 223 &b); 224 if (NULL == b) 225 { 226 GNUNET_break (0); 227 return false; 228 } 229 GNUNET_asprintf (&fn, 230 "%s/external-", 231 tmpdir); 232 stage->filename = GNUNET_DISK_mktemp (fn); 233 if (NULL == stage->filename) 234 { 235 GNUNET_break (0); 236 GNUNET_free (b); 237 GNUNET_free (fn); 238 return false; 239 } 240 GNUNET_free (fn); 241 fd = open (stage->filename, 242 O_WRONLY | O_TRUNC, 243 S_IRUSR | S_IWUSR); 244 if (-1 == fd) 245 { 246 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 247 "open", 248 stage->filename); 249 if (0 != unlink (stage->filename)) 250 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 251 "unlink", 252 stage->filename); 253 GNUNET_free (b); 254 GNUNET_free (stage->filename); 255 return false; 256 } 257 258 { 259 size_t off = 0; 260 261 while (off < n) 262 { 263 ssize_t r; 264 265 r = write (fd, 266 b + off, 267 n - off); 268 if (-1 == r) 269 { 270 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 271 "write", 272 stage->filename); 273 GNUNET_break (0 == close (fd)); 274 GNUNET_free (b); 275 GNUNET_free (stage->filename); 276 return false; 277 } 278 off += r; 279 } 280 } 281 GNUNET_break (0 == close (fd)); 282 GNUNET_free (b); 283 return true; 284 } 285 286 287 /** 288 * Generate a response for @a tc indicating an error of type @a ec. 289 * 290 * @param[in,out] tc context to fail 291 * @param ec error code to return 292 * @param hint hint text to return 293 */ 294 static void 295 typst_context_fail (struct TALER_MHD_TypstContext *tc, 296 enum TALER_ErrorCode ec, 297 const char *hint) 298 { 299 struct TALER_MHD_TypstResponse resp = { 300 .ec = ec, 301 .details.hint = hint 302 }; 303 304 if (NULL != tc->cb) 305 { 306 tc->cb (tc->cb_cls, 307 &resp); 308 tc->cb = NULL; 309 } 310 } 311 312 313 /** 314 * Helper task for typst_context_fail_async(). 315 * 316 * @param cls a `struct TALER_MHD_TypstContext` 317 */ 318 static void 319 fail_async_cb (void *cls) 320 { 321 struct TALER_MHD_TypstContext *tc = cls; 322 323 tc->t = NULL; 324 typst_context_fail (tc, 325 tc->async_ec, 326 tc->async_hint); 327 GNUNET_free (tc->async_hint); 328 TALER_MHD_typst_cancel (tc); 329 } 330 331 332 /** 333 * Generate a response for @a tc indicating an error of type @a ec. 334 * 335 * @param[in,out] tc context to fail 336 * @param ec error code to return 337 * @param hint hint text to return 338 */ 339 static void 340 typst_context_fail_async (struct TALER_MHD_TypstContext *tc, 341 enum TALER_ErrorCode ec, 342 const char *hint) 343 { 344 tc->async_ec = ec; 345 tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint); 346 tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb, 347 tc); 348 } 349 350 351 /** 352 * Called when the pdftk helper exited. 353 * 354 * @param cls our `struct TALER_MHD_TypstContext *` 355 * @param type type of the process 356 * @param exit_code status code of the process 357 */ 358 static void 359 pdftk_done_cb (void *cls, 360 enum GNUNET_OS_ProcessStatusType type, 361 long unsigned int exit_code) 362 { 363 struct TALER_MHD_TypstContext *tc = cls; 364 365 tc->cwh = NULL; 366 GNUNET_process_destroy (tc->proc); 367 tc->proc = NULL; 368 switch (type) 369 { 370 case GNUNET_OS_PROCESS_UNKNOWN: 371 GNUNET_assert (0); 372 return; 373 case GNUNET_OS_PROCESS_RUNNING: 374 /* we should not get this notification */ 375 GNUNET_break (0); 376 typst_context_fail (tc, 377 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 378 "pdftk in unexpected state"); 379 break; 380 case GNUNET_OS_PROCESS_STOPPED: 381 /* Someone is SIGSTOPing our helper!? */ 382 GNUNET_break (0); 383 typst_context_fail (tc, 384 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 385 "pdftk was stopped"); 386 break; 387 case GNUNET_OS_PROCESS_EXITED: 388 if (0 != exit_code) 389 { 390 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 391 "pdftk exited with status %d\n", 392 (int) exit_code); 393 typst_context_fail (tc, 394 TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE, 395 "pdftk failed"); 396 } 397 else 398 { 399 struct TALER_MHD_TypstResponse resp = { 400 .ec = TALER_EC_NONE, 401 .details.filename = tc->output_file, 402 }; 403 404 GNUNET_assert (NULL != tc->cb); 405 tc->cb (tc->cb_cls, 406 &resp); 407 tc->cb = NULL; 408 } 409 break; 410 case GNUNET_OS_PROCESS_SIGNALED: 411 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 412 "pdftk died with signal %d\n", 413 (int) exit_code); 414 typst_context_fail (tc, 415 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 416 "pdftk killed by signal"); 417 break; 418 } 419 TALER_MHD_typst_cancel (tc); 420 } 421 422 423 /** 424 * Function called once all of the individual stages are done. 425 * Triggers the pdftk run for @a tc. 426 * 427 * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for 428 */ 429 static void 430 complete_response (void *cls) 431 { 432 struct TALER_MHD_TypstContext *tc = cls; 433 const char *argv[tc->num_stages + 5]; 434 435 tc->t = NULL; 436 argv[0] = "pdftk"; 437 for (unsigned int i = 0; i<tc->num_stages; i++) 438 argv[i + 1] = tc->stages[i].filename; 439 argv[tc->num_stages + 1] = "cat"; 440 argv[tc->num_stages + 2] = "output"; 441 argv[tc->num_stages + 3] = tc->output_file; 442 argv[tc->num_stages + 4] = NULL; 443 tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 444 if (GNUNET_OK != 445 GNUNET_process_run_command_argv (tc->proc, 446 argv[0], 447 argv)) 448 { 449 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 450 "fork"); 451 GNUNET_process_destroy (tc->proc); 452 tc->proc = NULL; 453 /* The caller still holds @a tc until its callback runs, 454 so we must tell it before freeing @a tc. */ 455 typst_context_fail (tc, 456 TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE, 457 "could not start pdftk"); 458 TALER_MHD_typst_cancel (tc); 459 return; 460 } 461 tc->cwh = GNUNET_wait_child (tc->proc, 462 &pdftk_done_cb, 463 tc); 464 GNUNET_assert (NULL != tc->cwh); 465 } 466 467 468 /** 469 * Cancel typst. Wrapper task to do so asynchronously. 470 * 471 * @param[in] cls a `struct TALER_MHD_TypstContext` 472 */ 473 static void 474 cancel_async (void *cls) 475 { 476 struct TALER_MHD_TypstContext *tc = cls; 477 478 tc->t = NULL; 479 TALER_MHD_typst_cancel (tc); 480 } 481 482 483 /** 484 * Fail the typst context @a tc and clean it up asynchronously. 485 * 486 * Note that several stages may fail within the same SIGCHLD batch, in 487 * which case the clean-up task will already have been scheduled by the 488 * first failure; we must not schedule it a second time (and must not 489 * clean up synchronously, as we are called from the child-death 490 * notification). 491 * 492 * @param[in,out] tc context to fail 493 * @param ec error code to return to the client 494 * @param hint hint text to return to the client 495 */ 496 static void 497 stage_fail (struct TALER_MHD_TypstContext *tc, 498 enum TALER_ErrorCode ec, 499 const char *hint) 500 { 501 typst_context_fail (tc, 502 ec, 503 hint); 504 if (NULL == tc->t) 505 tc->t = GNUNET_SCHEDULER_add_now (&cancel_async, 506 tc); 507 } 508 509 510 /** 511 * Called when a typst helper exited. 512 * 513 * @param cls our `struct TypstStage *` 514 * @param type type of the process 515 * @param exit_code status code of the process 516 */ 517 static void 518 typst_done_cb (void *cls, 519 enum GNUNET_OS_ProcessStatusType type, 520 long unsigned int exit_code) 521 { 522 struct TypstStage *stage = cls; 523 struct TALER_MHD_TypstContext *tc = stage->tc; 524 525 stage->cwh = NULL; 526 GNUNET_process_destroy (stage->proc); 527 stage->proc = NULL; 528 switch (type) 529 { 530 case GNUNET_OS_PROCESS_UNKNOWN: 531 GNUNET_assert (0); 532 return; 533 case GNUNET_OS_PROCESS_RUNNING: 534 /* we should not get this notification */ 535 GNUNET_break (0); 536 stage_fail (tc, 537 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 538 "Typst in unexpected state"); 539 return; 540 case GNUNET_OS_PROCESS_STOPPED: 541 /* Someone is SIGSTOPing our helper!? */ 542 GNUNET_break (0); 543 stage_fail (tc, 544 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 545 "Typst was stopped"); 546 return; 547 case GNUNET_OS_PROCESS_EXITED: 548 if (0 != exit_code) 549 { 550 char err[128]; 551 552 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 553 "typst exited with status %d\n", 554 (int) exit_code); 555 GNUNET_snprintf (err, 556 sizeof (err), 557 "Typst exited with status %d", 558 (int) exit_code); 559 stage_fail (tc, 560 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 561 err); 562 return; 563 } 564 break; 565 case GNUNET_OS_PROCESS_SIGNALED: 566 { 567 char err[128]; 568 569 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 570 "typst died with signal %d\n", 571 (int) exit_code); 572 GNUNET_snprintf (err, 573 sizeof (err), 574 "Typst died with signal %d", 575 (int) exit_code); 576 stage_fail (tc, 577 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 578 err); 579 return; 580 } 581 break; 582 } 583 tc->active_stages--; 584 if (NULL != stage->proc) 585 { 586 GNUNET_process_destroy (stage->proc); 587 stage->proc = NULL; 588 } 589 if (0 != tc->active_stages) 590 return; 591 if (NULL != tc->t) 592 return; /* clean-up of a failed stage is already scheduled */ 593 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 594 tc); 595 } 596 597 598 /** 599 * Check that @a id only consists of characters that are safe to 600 * interpolate into a Typst import statement (alphanumerics, dots, 601 * hyphens and underscores). This prevents code/path injection via the form 602 * name or version. 603 * 604 * @param id identifier to validate 605 * @return true if @a id is non-empty and only uses the safe charset 606 */ 607 static bool 608 is_valid_form_identifier (const char *id) 609 { 610 if ( (NULL == id) || 611 ('\0' == id[0]) ) 612 return false; 613 for (const char *p = id; '\0' != *p; p++) 614 { 615 char c = *p; 616 617 if (! ( ( ('a' <= c) && ('z' >= c)) || 618 ( ('A' <= c) && ('Z' >= c)) || 619 ( ('0' <= c) && ('9' >= c)) || 620 ('.' == c) || 621 ('-' == c) || 622 ('_' == c) ) ) 623 return false; 624 } 625 return true; 626 } 627 628 629 /** 630 * Setup typst stage to produce one of the PDF inputs. 631 * 632 * @param[out] stage initialized stage data 633 * @param i index of the stage 634 * @param tmpdir where to place temporary files 635 * @param template_path where to find templates 636 * @param doc input document specification 637 * @return true on success 638 */ 639 static bool 640 setup_stage (struct TypstStage *stage, 641 unsigned int i, 642 const char *tmpdir, 643 const char *template_path, 644 const struct TALER_MHD_TypstDocument *doc) 645 { 646 struct TALER_MHD_TypstContext *tc = stage->tc; 647 char *input; 648 bool is_dot_typ; 649 650 if (NULL == doc->form_name) 651 { 652 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 653 "Stage %u: Dumping inlined PDF attachment\n", 654 i); 655 return inline_pdf_stage (stage, 656 tmpdir, 657 doc->data); 658 } 659 660 if (! is_valid_form_identifier (doc->form_name)) 661 { 662 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 663 "Stage %u: Invalid form name `%s'\n", 664 i, 665 doc->form_name); 666 return false; 667 } 668 if ( (NULL != doc->form_version) && 669 (! is_valid_form_identifier (doc->form_version)) ) 670 { 671 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 672 "Stage %u: Invalid form version `%s'\n", 673 i, 674 doc->form_version); 675 return false; 676 } 677 678 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 679 "Stage %u: Handling form %s\n", 680 i, 681 doc->form_name); 682 683 /* Setup inputs */ 684 { 685 char *dirname; 686 687 GNUNET_asprintf (&dirname, 688 "%s/%u/", 689 tmpdir, 690 i); 691 if (GNUNET_OK != 692 GNUNET_DISK_directory_create (dirname)) 693 { 694 GNUNET_free (dirname); 695 return false; 696 } 697 GNUNET_free (dirname); 698 } 699 700 /* Setup data input */ 701 { 702 char *jfn; 703 704 GNUNET_asprintf (&jfn, 705 "%s/%u/input.json", 706 tmpdir, 707 i); 708 if (0 != 709 json_dump_file (doc->data, 710 jfn, 711 JSON_INDENT (2))) 712 { 713 GNUNET_break (0); 714 GNUNET_free (jfn); 715 return false; 716 } 717 GNUNET_free (jfn); 718 } 719 720 /* setup output file name */ 721 GNUNET_asprintf (&stage->filename, 722 "%s/%u/input.pdf", 723 tmpdir, 724 i); 725 726 /* setup main input Typst file */ 727 { 728 char *intyp; 729 size_t slen = strlen (doc->form_name); 730 731 is_dot_typ = ( (slen > 4) && 732 (0 == memcmp (&doc->form_name[slen - 4], 733 ".typ", 734 4)) ); 735 /* We do not append the ":$VERSION" if a filename ending with ".typ" 736 is given. Otherwise we append the version, or ":0.0.0" if no 737 explicit version is given. */ 738 GNUNET_asprintf (&intyp, 739 "#import \"%s/%s%s%s\": form\n" 740 "#form(json(\"input.json\"))\n", 741 template_path, 742 doc->form_name, 743 is_dot_typ ? "" : ":", 744 is_dot_typ 745 ? "" 746 : ( (NULL == doc->form_version) 747 ? "0.0.0" 748 : doc->form_version)); 749 GNUNET_asprintf (&input, 750 "%s/%u/input.typ", 751 tmpdir, 752 i); 753 if (GNUNET_OK != 754 GNUNET_DISK_fn_write (input, 755 intyp, 756 strlen (intyp), 757 GNUNET_DISK_PERM_USER_READ)) 758 { 759 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 760 "write", 761 input); 762 GNUNET_free (input); 763 GNUNET_free (intyp); 764 return false; 765 } 766 GNUNET_free (intyp); 767 } 768 769 /* now setup typst invocation */ 770 { 771 const char *argv[6]; 772 773 if (is_dot_typ) 774 { 775 /* This deliberately breaks the typst sandbox. Why? Because Typst sucks 776 and does not support multiple roots, but here we have dynamic data in 777 /tmp and a style file outside of /tmp (and copying is also not 778 practical as we don't know what all to copy). Typst should really 779 support multiple roots. Anyway, in production this path should not 780 happen, because there we use Typst packages. */ 781 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 782 "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n", 783 doc->form_name); 784 argv[0] = "typst"; 785 argv[1] = "compile"; 786 argv[2] = "--root"; 787 argv[3] = "/"; 788 argv[4] = input; 789 argv[5] = NULL; 790 } 791 else 792 { 793 argv[0] = "typst"; 794 argv[1] = "compile"; 795 argv[2] = input; 796 argv[3] = NULL; 797 } 798 stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 799 { 800 char *datadir; 801 802 datadir = GNUNET_OS_installation_get_path ( 803 tc->pd, 804 GNUNET_OS_IPK_DATADIR); 805 GNUNET_assert (GNUNET_OK == 806 GNUNET_process_set_options ( 807 stage->proc, 808 GNUNET_process_option_set_environment ("XDG_DATA_HOME", 809 datadir))); 810 GNUNET_free (datadir); 811 } 812 if (GNUNET_OK != 813 GNUNET_process_run_command_argv (stage->proc, 814 "typst", 815 argv)) 816 { 817 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 818 "fork"); 819 GNUNET_free (input); 820 GNUNET_process_destroy (stage->proc); 821 stage->proc = NULL; 822 return false; 823 } 824 GNUNET_free (input); 825 tc->active_stages++; 826 stage->cwh = GNUNET_wait_child (stage->proc, 827 &typst_done_cb, 828 stage); 829 GNUNET_assert (NULL != stage->cwh); 830 } 831 return true; 832 } 833 834 835 struct TALER_MHD_TypstContext * 836 TALER_MHD_typst ( 837 const struct GNUNET_OS_ProjectData *pd, 838 const struct GNUNET_CONFIGURATION_Handle *cfg, 839 bool remove_on_exit, 840 const char *cfg_section_name, 841 unsigned int num_documents, 842 const struct TALER_MHD_TypstDocument docs[static num_documents], 843 TALER_MHD_TypstResultCallback cb, 844 void *cb_cls) 845 { 846 static enum GNUNET_GenericReturnValue once = GNUNET_NO; 847 struct TALER_MHD_TypstContext *tc; 848 849 switch (once) 850 { 851 case GNUNET_OK: 852 break; 853 case GNUNET_NO: 854 if (GNUNET_SYSERR == 855 GNUNET_OS_check_helper_binary ("typst", 856 false, 857 NULL)) 858 { 859 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 860 "`typst' command not found\n"); 861 once = GNUNET_SYSERR; 862 return NULL; 863 } 864 if (GNUNET_SYSERR == 865 GNUNET_OS_check_helper_binary ("pdftk", 866 false, 867 NULL)) 868 { 869 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 870 "`pdftk' command not found\n"); 871 once = GNUNET_SYSERR; 872 return NULL; 873 } 874 once = GNUNET_OK; 875 break; 876 case GNUNET_SYSERR: 877 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 878 "PDF generation initialization failed before, not even trying again\n"); 879 return NULL; 880 } 881 tc = GNUNET_new (struct TALER_MHD_TypstContext); 882 tc->pd = pd; 883 tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX"); 884 tc->remove_on_exit = remove_on_exit; 885 tc->cb = cb; 886 tc->cb_cls = cb_cls; 887 if (NULL == mkdtemp (tc->tmpdir)) 888 { 889 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 890 "mkdtemp", 891 tc->tmpdir); 892 GNUNET_free (tc->tmpdir); 893 TALER_MHD_typst_cancel (tc); 894 return NULL; 895 } 896 GNUNET_asprintf (&tc->output_file, 897 "%s/final.pdf", 898 tc->tmpdir); 899 900 /* setup typst stages */ 901 { 902 char *template_path; 903 904 if (GNUNET_OK != 905 GNUNET_CONFIGURATION_get_value_string (cfg, 906 cfg_section_name, 907 "TYPST_TEMPLATES", 908 &template_path)) 909 { 910 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 911 cfg_section_name, 912 "TYPST_TEMPLATES"); 913 TALER_MHD_typst_cancel (tc); 914 return NULL; 915 } 916 if ('@' != template_path[0]) 917 template_path = GNUNET_CONFIGURATION_expand_dollar (cfg, 918 template_path); 919 tc->stages = GNUNET_new_array (num_documents, 920 struct TypstStage); 921 tc->num_stages = num_documents; 922 for (unsigned int i = 0; i<num_documents; i++) 923 { 924 tc->stages[i].tc = tc; 925 if (! setup_stage (&tc->stages[i], 926 i, 927 tc->tmpdir, 928 template_path, 929 &docs[i])) 930 { 931 char err[128]; 932 933 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 934 "Typst setup failed on stage %u\n", 935 i); 936 GNUNET_snprintf (err, 937 sizeof (err), 938 "Typst setup failed on stage %u", 939 i); 940 typst_context_fail_async (tc, 941 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 942 err); 943 GNUNET_free (template_path); 944 return tc; 945 } 946 } 947 GNUNET_free (template_path); 948 } 949 if (0 == tc->active_stages) 950 { 951 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 952 tc); 953 } 954 return tc; 955 } 956 957 958 struct MHD_Response * 959 TALER_MHD_response_from_pdf_file (const char *filename) 960 { 961 struct MHD_Response *resp; 962 struct stat s; 963 int fd; 964 965 fd = open (filename, 966 O_RDONLY); 967 if (-1 == fd) 968 { 969 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 970 "open", 971 filename); 972 return NULL; 973 } 974 if (0 != 975 fstat (fd, 976 &s)) 977 { 978 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 979 "fstat", 980 filename); 981 GNUNET_assert (0 == close (fd)); 982 return NULL; 983 } 984 resp = MHD_create_response_from_fd (s.st_size, 985 fd); 986 if (NULL == resp) 987 { 988 GNUNET_break (0); 989 GNUNET_assert (0 == close (fd)); 990 return NULL; 991 } 992 TALER_MHD_add_global_headers (resp, 993 false); 994 GNUNET_break (MHD_YES == 995 MHD_add_response_header (resp, 996 MHD_HTTP_HEADER_CONTENT_TYPE, 997 "application/pdf")); 998 return resp; 999 }