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