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