mhd_typst.c (22922B)
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 "taler/platform.h" 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 GNUNET_free (b); 239 GNUNET_free (stage->filename); 240 return false; 241 } 242 243 { 244 size_t off = 0; 245 246 while (off < n) 247 { 248 ssize_t r; 249 250 r = write (fd, 251 b + off, 252 n - off); 253 if (-1 == r) 254 { 255 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 256 "write", 257 stage->filename); 258 GNUNET_break (0 == close (fd)); 259 GNUNET_free (b); 260 GNUNET_free (stage->filename); 261 return false; 262 } 263 off += r; 264 } 265 } 266 GNUNET_break (0 == close (fd)); 267 return true; 268 } 269 270 271 /** 272 * Generate a response for @a tc indicating an error of type @a ec. 273 * 274 * @param[in,out] tc context to fail 275 * @param ec error code to return 276 * @param hint hint text to return 277 */ 278 static void 279 typst_context_fail (struct TALER_MHD_TypstContext *tc, 280 enum TALER_ErrorCode ec, 281 const char *hint) 282 { 283 struct TALER_MHD_TypstResponse resp = { 284 .ec = ec, 285 .details.hint = hint 286 }; 287 288 if (NULL != tc->cb) 289 { 290 tc->cb (tc->cb_cls, 291 &resp); 292 tc->cb = NULL; 293 } 294 } 295 296 297 /** 298 * Helper task for typst_context_fail_async(). 299 * 300 * @param cls a `struct TALER_MHD_TypstContext` 301 */ 302 static void 303 fail_async_cb (void *cls) 304 { 305 struct TALER_MHD_TypstContext *tc = cls; 306 307 tc->t = NULL; 308 typst_context_fail (tc, 309 tc->async_ec, 310 tc->async_hint); 311 GNUNET_free (tc->async_hint); 312 TALER_MHD_typst_cancel (tc); 313 } 314 315 316 /** 317 * Generate a response for @a tc indicating an error of type @a ec. 318 * 319 * @param[in,out] tc context to fail 320 * @param ec error code to return 321 * @param hint hint text to return 322 */ 323 static void 324 typst_context_fail_async (struct TALER_MHD_TypstContext *tc, 325 enum TALER_ErrorCode ec, 326 const char *hint) 327 { 328 tc->async_ec = ec; 329 tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint); 330 tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb, 331 tc); 332 } 333 334 335 /** 336 * Called when the pdftk helper exited. 337 * 338 * @param cls our `struct TALER_MHD_TypstContext *` 339 * @param type type of the process 340 * @param exit_code status code of the process 341 */ 342 static void 343 pdftk_done_cb (void *cls, 344 enum GNUNET_OS_ProcessStatusType type, 345 long unsigned int exit_code) 346 { 347 struct TALER_MHD_TypstContext *tc = cls; 348 349 tc->cwh = NULL; 350 GNUNET_process_destroy (tc->proc); 351 tc->proc = NULL; 352 switch (type) 353 { 354 case GNUNET_OS_PROCESS_UNKNOWN: 355 GNUNET_assert (0); 356 return; 357 case GNUNET_OS_PROCESS_RUNNING: 358 /* we should not get this notification */ 359 GNUNET_break (0); 360 return; 361 case GNUNET_OS_PROCESS_STOPPED: 362 /* Someone is SIGSTOPing our helper!? */ 363 GNUNET_break (0); 364 return; 365 case GNUNET_OS_PROCESS_EXITED: 366 if (0 != exit_code) 367 { 368 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 369 "pdftk exited with status %d\n", 370 (int) exit_code); 371 typst_context_fail (tc, 372 TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE, 373 "pdftk failed"); 374 } 375 else 376 { 377 struct TALER_MHD_TypstResponse resp = { 378 .ec = TALER_EC_NONE, 379 .details.filename = tc->output_file, 380 }; 381 382 GNUNET_assert (NULL != tc->cb); 383 tc->cb (tc->cb_cls, 384 &resp); 385 tc->cb = NULL; 386 } 387 break; 388 case GNUNET_OS_PROCESS_SIGNALED: 389 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 390 "pdftk died with signal %d\n", 391 (int) exit_code); 392 typst_context_fail (tc, 393 TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH, 394 "pdftk killed by signal"); 395 break; 396 } 397 TALER_MHD_typst_cancel (tc); 398 } 399 400 401 /** 402 * Function called once all of the individual stages are done. 403 * Triggers the pdftk run for @a tc. 404 * 405 * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for 406 */ 407 static void 408 complete_response (void *cls) 409 { 410 struct TALER_MHD_TypstContext *tc = cls; 411 const char *argv[tc->num_stages + 5]; 412 413 tc->t = NULL; 414 argv[0] = "pdftk"; 415 for (unsigned int i = 0; i<tc->num_stages; i++) 416 argv[i + 1] = tc->stages[i].filename; 417 argv[tc->num_stages + 1] = "cat"; 418 argv[tc->num_stages + 2] = "output"; 419 argv[tc->num_stages + 3] = tc->output_file; 420 argv[tc->num_stages + 4] = NULL; 421 tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 422 if (GNUNET_OK != 423 GNUNET_process_run_command_argv (tc->proc, 424 argv[0], 425 argv)) 426 { 427 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 428 "fork"); 429 GNUNET_process_destroy (tc->proc); 430 tc->proc = NULL; 431 TALER_MHD_typst_cancel (tc); 432 return; 433 } 434 tc->cwh = GNUNET_wait_child (tc->proc, 435 &pdftk_done_cb, 436 tc); 437 GNUNET_assert (NULL != tc->cwh); 438 } 439 440 441 /** 442 * Cancel typst. Wrapper task to do so asynchronously. 443 * 444 * @param[in] cls a `struct TALER_MHD_TypstContext` 445 */ 446 static void 447 cancel_async (void *cls) 448 { 449 struct TALER_MHD_TypstContext *tc = cls; 450 451 tc->t = NULL; 452 TALER_MHD_typst_cancel (tc); 453 } 454 455 456 /** 457 * Called when a typst helper exited. 458 * 459 * @param cls our `struct TypstStage *` 460 * @param type type of the process 461 * @param exit_code status code of the process 462 */ 463 static void 464 typst_done_cb (void *cls, 465 enum GNUNET_OS_ProcessStatusType type, 466 long unsigned int exit_code) 467 { 468 struct TypstStage *stage = cls; 469 struct TALER_MHD_TypstContext *tc = stage->tc; 470 471 stage->cwh = NULL; 472 GNUNET_process_destroy (stage->proc); 473 stage->proc = NULL; 474 switch (type) 475 { 476 case GNUNET_OS_PROCESS_UNKNOWN: 477 GNUNET_assert (0); 478 return; 479 case GNUNET_OS_PROCESS_RUNNING: 480 /* we should not get this notification */ 481 GNUNET_break (0); 482 return; 483 case GNUNET_OS_PROCESS_STOPPED: 484 /* Someone is SIGSTOPing our helper!? */ 485 GNUNET_break (0); 486 return; 487 case GNUNET_OS_PROCESS_EXITED: 488 if (0 != exit_code) 489 { 490 char err[128]; 491 492 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 493 "typst exited with status %d\n", 494 (int) exit_code); 495 GNUNET_snprintf (err, 496 sizeof (err), 497 "Typst exited with status %d", 498 (int) exit_code); 499 typst_context_fail (tc, 500 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 501 err); 502 GNUNET_assert (NULL == tc->t); 503 tc->t = GNUNET_SCHEDULER_add_now (&cancel_async, 504 tc); 505 return; 506 } 507 break; 508 case GNUNET_OS_PROCESS_SIGNALED: 509 { 510 char err[128]; 511 512 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 513 "typst died with signal %d\n", 514 (int) exit_code); 515 GNUNET_snprintf (err, 516 sizeof (err), 517 "Typst died with signal %d", 518 (int) exit_code); 519 typst_context_fail (tc, 520 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH, 521 err); 522 GNUNET_assert (NULL == tc->t); 523 tc->t = GNUNET_SCHEDULER_add_now (&cancel_async, 524 tc); 525 return; 526 } 527 break; 528 } 529 tc->active_stages--; 530 if (NULL != stage->proc) 531 { 532 GNUNET_process_destroy (stage->proc); 533 stage->proc = NULL; 534 } 535 if (0 != tc->active_stages) 536 return; 537 GNUNET_assert (NULL == tc->t); 538 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 539 tc); 540 } 541 542 543 /** 544 * Setup typst stage to produce one of the PDF inputs. 545 * 546 * @param[out] stage initialized stage data 547 * @param i index of the stage 548 * @param tmpdir where to place temporary files 549 * @param template_path where to find templates 550 * @param doc input document specification 551 * @return true on success 552 */ 553 static bool 554 setup_stage (struct TypstStage *stage, 555 unsigned int i, 556 const char *tmpdir, 557 const char *template_path, 558 const struct TALER_MHD_TypstDocument *doc) 559 { 560 struct TALER_MHD_TypstContext *tc = stage->tc; 561 char *input; 562 bool is_dot_typ; 563 564 if (NULL == doc->form_name) 565 { 566 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 567 "Stage %u: Dumping inlined PDF attachment\n", 568 i); 569 return inline_pdf_stage (stage, 570 tmpdir, 571 doc->data); 572 } 573 574 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 575 "Stage %u: Handling form %s\n", 576 i, 577 doc->form_name); 578 579 /* Setup inputs */ 580 { 581 char *dirname; 582 583 GNUNET_asprintf (&dirname, 584 "%s/%u/", 585 tmpdir, 586 i); 587 if (GNUNET_OK != 588 GNUNET_DISK_directory_create (dirname)) 589 { 590 GNUNET_free (dirname); 591 return false; 592 } 593 GNUNET_free (dirname); 594 } 595 596 /* Setup data input */ 597 { 598 char *jfn; 599 600 GNUNET_asprintf (&jfn, 601 "%s/%u/input.json", 602 tmpdir, 603 i); 604 if (0 != 605 json_dump_file (doc->data, 606 jfn, 607 JSON_INDENT (2))) 608 { 609 GNUNET_break (0); 610 GNUNET_free (jfn); 611 return false; 612 } 613 GNUNET_free (jfn); 614 } 615 616 /* setup output file name */ 617 GNUNET_asprintf (&stage->filename, 618 "%s/%u/input.pdf", 619 tmpdir, 620 i); 621 622 /* setup main input Typst file */ 623 { 624 char *intyp; 625 size_t slen = strlen (doc->form_name); 626 627 is_dot_typ = ( (slen > 4) && 628 (0 == memcmp (&doc->form_name[slen - 4], 629 ".typ", 630 4)) ); 631 /* We do not append the ":$VERSION" if a filename ending with ".typ" 632 is given. Otherwise we append the version, or ":0.0.0" if no 633 explicit version is given. */ 634 GNUNET_asprintf (&intyp, 635 "#import \"%s/%s%s%s\": form\n" 636 "#form(json(\"input.json\"))\n", 637 template_path, 638 doc->form_name, 639 is_dot_typ ? "" : ":", 640 is_dot_typ 641 ? "" 642 : ( (NULL == doc->form_version) 643 ? "0.0.0" 644 : doc->form_version)); 645 GNUNET_asprintf (&input, 646 "%s/%u/input.typ", 647 tmpdir, 648 i); 649 if (GNUNET_OK != 650 GNUNET_DISK_fn_write (input, 651 intyp, 652 strlen (intyp), 653 GNUNET_DISK_PERM_USER_READ)) 654 { 655 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 656 "write", 657 input); 658 GNUNET_free (input); 659 GNUNET_free (intyp); 660 return false; 661 } 662 GNUNET_free (intyp); 663 } 664 665 /* now setup typst invocation */ 666 { 667 const char *argv[6]; 668 669 if (is_dot_typ) 670 { 671 /* This deliberately breaks the typst sandbox. Why? Because Typst sucks 672 and does not support multiple roots, but here we have dynamic data in 673 /tmp and a style file outside of /tmp (and copying is also not 674 practical as we don't know what all to copy). Typst should really 675 support multiple roots. Anyway, in production this path should not 676 happen, because there we use Typst packages. */ 677 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 678 "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n", 679 doc->form_name); 680 argv[0] = "typst"; 681 argv[1] = "compile"; 682 argv[2] = "--root"; 683 argv[3] = "/"; 684 argv[4] = input; 685 argv[5] = NULL; 686 } 687 else 688 { 689 argv[0] = "typst"; 690 argv[1] = "compile"; 691 argv[2] = input; 692 argv[3] = NULL; 693 } 694 stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 695 { 696 char *datadir; 697 698 datadir = GNUNET_OS_installation_get_path ( 699 tc->pd, 700 GNUNET_OS_IPK_DATADIR); 701 GNUNET_assert (GNUNET_OK == 702 GNUNET_process_set_options ( 703 stage->proc, 704 GNUNET_process_option_set_environment ("XDG_DATA_HOME", 705 datadir))); 706 GNUNET_free (datadir); 707 } 708 if (GNUNET_OK != 709 GNUNET_process_run_command_argv (stage->proc, 710 "typst", 711 argv)) 712 { 713 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 714 "fork"); 715 GNUNET_free (input); 716 GNUNET_process_destroy (stage->proc); 717 stage->proc = NULL; 718 return false; 719 } 720 GNUNET_free (input); 721 tc->active_stages++; 722 stage->cwh = GNUNET_wait_child (stage->proc, 723 &typst_done_cb, 724 stage); 725 GNUNET_assert (NULL != stage->cwh); 726 } 727 return true; 728 } 729 730 731 struct TALER_MHD_TypstContext * 732 TALER_MHD_typst ( 733 const struct GNUNET_OS_ProjectData *pd, 734 const struct GNUNET_CONFIGURATION_Handle *cfg, 735 bool remove_on_exit, 736 const char *cfg_section_name, 737 unsigned int num_documents, 738 const struct TALER_MHD_TypstDocument docs[static num_documents], 739 TALER_MHD_TypstResultCallback cb, 740 void *cb_cls) 741 { 742 static enum GNUNET_GenericReturnValue once = GNUNET_NO; 743 struct TALER_MHD_TypstContext *tc; 744 745 switch (once) 746 { 747 case GNUNET_OK: 748 break; 749 case GNUNET_NO: 750 if (GNUNET_SYSERR == 751 GNUNET_OS_check_helper_binary ("typst", 752 false, 753 NULL)) 754 { 755 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 756 "`typst' command not found\n"); 757 once = GNUNET_SYSERR; 758 return NULL; 759 } 760 if (GNUNET_SYSERR == 761 GNUNET_OS_check_helper_binary ("pdftk", 762 false, 763 NULL)) 764 { 765 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 766 "`pdftk' command not found\n"); 767 once = GNUNET_SYSERR; 768 return NULL; 769 } 770 once = GNUNET_OK; 771 break; 772 case GNUNET_SYSERR: 773 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 774 "PDF generation initialization failed before, not even trying again\n"); 775 return NULL; 776 } 777 tc = GNUNET_new (struct TALER_MHD_TypstContext); 778 tc->pd = pd; 779 tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX"); 780 tc->remove_on_exit = remove_on_exit; 781 tc->cb = cb; 782 tc->cb_cls = cb_cls; 783 if (NULL == mkdtemp (tc->tmpdir)) 784 { 785 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 786 "mkdtemp", 787 tc->tmpdir); 788 GNUNET_free (tc->tmpdir); 789 TALER_MHD_typst_cancel (tc); 790 return NULL; 791 } 792 GNUNET_asprintf (&tc->output_file, 793 "%s/final.pdf", 794 tc->tmpdir); 795 796 /* setup typst stages */ 797 { 798 char *template_path; 799 800 if (GNUNET_OK != 801 GNUNET_CONFIGURATION_get_value_string (cfg, 802 cfg_section_name, 803 "TYPST_TEMPLATES", 804 &template_path)) 805 { 806 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 807 cfg_section_name, 808 "TYPST_TEMPLATES"); 809 TALER_MHD_typst_cancel (tc); 810 return NULL; 811 } 812 if ('@' != template_path[0]) 813 template_path = GNUNET_CONFIGURATION_expand_dollar (cfg, 814 template_path); 815 tc->stages = GNUNET_new_array (num_documents, 816 struct TypstStage); 817 tc->num_stages = num_documents; 818 for (unsigned int i = 0; i<num_documents; i++) 819 { 820 tc->stages[i].tc = tc; 821 if (! setup_stage (&tc->stages[i], 822 i, 823 tc->tmpdir, 824 template_path, 825 &docs[i])) 826 { 827 char err[128]; 828 829 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 830 "Typst setup failed on stage %u\n", 831 i); 832 GNUNET_snprintf (err, 833 sizeof (err), 834 "Typst setup failed on stage %u", 835 i); 836 typst_context_fail_async (tc, 837 TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE, 838 err); 839 return tc; 840 } 841 } 842 GNUNET_free (template_path); 843 } 844 if (0 == tc->active_stages) 845 { 846 tc->t = GNUNET_SCHEDULER_add_now (&complete_response, 847 tc); 848 } 849 return tc; 850 } 851 852 853 struct MHD_Response * 854 TALER_MHD_response_from_pdf_file (const char *filename) 855 { 856 struct MHD_Response *resp; 857 struct stat s; 858 int fd; 859 860 fd = open (filename, 861 O_RDONLY); 862 if (-1 == fd) 863 { 864 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 865 "open", 866 filename); 867 return NULL; 868 } 869 if (0 != 870 fstat (fd, 871 &s)) 872 { 873 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 874 "fstat", 875 filename); 876 GNUNET_assert (0 == close (fd)); 877 return NULL; 878 } 879 resp = MHD_create_response_from_fd (s.st_size, 880 fd); 881 TALER_MHD_add_global_headers (resp, 882 false); 883 GNUNET_break (MHD_YES == 884 MHD_add_response_header (resp, 885 MHD_HTTP_HEADER_CONTENT_TYPE, 886 "application/pdf")); 887 return resp; 888 }