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