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 : argv[0],
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 : {
555 0 : GNUNET_break (0);
556 0 : GNUNET_free (jfn);
557 0 : return false;
558 : }
559 0 : GNUNET_free (jfn);
560 : }
561 :
562 : /* setup output file name */
563 0 : GNUNET_asprintf (&stage->filename,
564 : "%s/%u/input.pdf",
565 : tmpdir,
566 : i);
567 :
568 : /* setup main input Typst file */
569 : {
570 : char *intyp;
571 : char *template_fn;
572 :
573 0 : GNUNET_asprintf (&template_fn,
574 : "%s%s.typ",
575 : template_path,
576 0 : doc->form_name);
577 0 : if (GNUNET_YES !=
578 0 : GNUNET_DISK_file_test_read (template_fn))
579 : {
580 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
581 : "access",
582 : template_fn);
583 0 : GNUNET_free (template_fn);
584 0 : return false;
585 : }
586 0 : GNUNET_asprintf (&intyp,
587 : "#import \"%s\": form\n"
588 : "#form(json(\"%s/%u/input.json\"))\n",
589 : template_fn,
590 : tmpdir,
591 : i);
592 0 : GNUNET_asprintf (&input,
593 : "%s/%u/input.typ",
594 : tmpdir,
595 : i);
596 0 : if (GNUNET_OK !=
597 0 : GNUNET_DISK_fn_write (input,
598 : intyp,
599 : strlen (intyp),
600 : GNUNET_DISK_PERM_USER_READ))
601 : {
602 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
603 : "write",
604 : input);
605 0 : GNUNET_free (input);
606 0 : GNUNET_free (intyp);
607 0 : GNUNET_free (template_fn);
608 0 : return false;
609 : }
610 0 : GNUNET_free (template_fn);
611 0 : GNUNET_free (intyp);
612 : }
613 :
614 : /* now setup typst invocation */
615 : {
616 : const char *argv[6];
617 :
618 0 : argv[0] = "typst";
619 0 : argv[1] = "compile";
620 : /* This deliberately breaks the typst sandbox. Why? Because
621 : they suck and do not support multiple roots, but we have
622 : dynamic data in /tmp and resources outside of /tmp and
623 : copying all the time is also bad. Typst should really
624 : support multiple roots. */
625 0 : argv[2] = "--root";
626 0 : argv[3] = "/";
627 0 : argv[4] = input;
628 0 : argv[5] = NULL;
629 0 : stage->proc = GNUNET_OS_start_process_vap (
630 : GNUNET_OS_INHERIT_STD_ERR,
631 : NULL,
632 : NULL,
633 : NULL,
634 : "typst",
635 : (char **) argv);
636 0 : if (NULL == stage->proc)
637 : {
638 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
639 : "fork");
640 0 : GNUNET_free (input);
641 0 : return false;
642 : }
643 0 : GNUNET_free (input);
644 0 : stage->tc->active_stages++;
645 0 : stage->cwh = GNUNET_wait_child (stage->proc,
646 : &typst_done_cb,
647 : stage);
648 0 : GNUNET_assert (NULL != stage->cwh);
649 : }
650 0 : return true;
651 : }
652 :
653 :
654 : struct TALER_MHD_TypstContext *
655 1 : TALER_MHD_typst (
656 : const struct GNUNET_CONFIGURATION_Handle *cfg,
657 : bool remove_on_exit,
658 : const char *cfg_section_name,
659 : unsigned int num_documents,
660 : const struct TALER_MHD_TypstDocument docs[static num_documents],
661 : TALER_MHD_TypstResultCallback cb,
662 : void *cb_cls)
663 1 : {
664 : static enum GNUNET_GenericReturnValue once = GNUNET_NO;
665 : struct TALER_MHD_TypstContext *tc;
666 :
667 1 : switch (once)
668 : {
669 0 : case GNUNET_OK:
670 0 : break;
671 1 : case GNUNET_NO:
672 1 : if (GNUNET_SYSERR ==
673 1 : GNUNET_OS_check_helper_binary ("typst",
674 : false,
675 : NULL))
676 : {
677 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
678 : "`typst' command not found\n");
679 1 : once = GNUNET_SYSERR;
680 1 : return NULL;
681 : }
682 0 : if (GNUNET_SYSERR ==
683 0 : GNUNET_OS_check_helper_binary ("pdftk",
684 : false,
685 : NULL))
686 : {
687 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
688 : "`pdftk' command not found\n");
689 0 : once = GNUNET_SYSERR;
690 0 : return NULL;
691 : }
692 0 : once = GNUNET_OK;
693 0 : break;
694 0 : case GNUNET_SYSERR:
695 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
696 : "PDF generation initialization failed before, not even trying again\n");
697 0 : return NULL;
698 : }
699 0 : tc = GNUNET_new (struct TALER_MHD_TypstContext);
700 0 : tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
701 0 : tc->remove_on_exit = remove_on_exit;
702 0 : tc->cb = cb;
703 0 : tc->cb_cls = cb_cls;
704 0 : if (NULL == mkdtemp (tc->tmpdir))
705 : {
706 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
707 : "mkdtemp",
708 : tc->tmpdir);
709 0 : GNUNET_free (tc->tmpdir);
710 0 : TALER_MHD_typst_cancel (tc);
711 0 : return NULL;
712 : }
713 0 : GNUNET_asprintf (&tc->output_file,
714 : "%s/final.pdf",
715 : tc->tmpdir);
716 :
717 : /* setup typst stages */
718 : {
719 : char *template_path;
720 :
721 0 : if (GNUNET_OK !=
722 0 : GNUNET_CONFIGURATION_get_value_filename (cfg,
723 : cfg_section_name,
724 : "TYPST_TEMPLATES",
725 : &template_path))
726 : {
727 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
728 : cfg_section_name,
729 : "TYPST_TEMPLATES");
730 0 : TALER_MHD_typst_cancel (tc);
731 0 : return NULL;
732 : }
733 0 : tc->stages = GNUNET_new_array (num_documents,
734 : struct TypstStage);
735 0 : tc->num_stages = num_documents;
736 0 : for (unsigned int i = 0; i<num_documents; i++)
737 : {
738 0 : tc->stages[i].tc = tc;
739 0 : if (! setup_stage (&tc->stages[i],
740 : i,
741 0 : tc->tmpdir,
742 : template_path,
743 0 : &docs[i]))
744 : {
745 : char err[128];
746 :
747 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
748 : "Typst setup failed on stage %u\n",
749 : i);
750 0 : GNUNET_snprintf (err,
751 : sizeof (err),
752 : "Typst setup failed on stage %u",
753 : i);
754 0 : typst_context_fail (tc,
755 : TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
756 : err);
757 0 : TALER_MHD_typst_cancel (tc);
758 0 : return NULL;
759 : }
760 : }
761 0 : GNUNET_free (template_path);
762 : }
763 0 : if (0 == tc->active_stages)
764 : {
765 0 : tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
766 : tc);
767 : }
768 0 : return tc;
769 : }
770 :
771 :
772 : struct MHD_Response *
773 0 : TALER_MHD_response_from_pdf_file (const char *filename)
774 : {
775 : struct MHD_Response *resp;
776 : struct stat s;
777 : int fd;
778 :
779 0 : fd = open (filename,
780 : O_RDONLY);
781 0 : if (-1 == fd)
782 : {
783 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
784 : "open",
785 : filename);
786 0 : return NULL;
787 : }
788 0 : if (0 !=
789 0 : fstat (fd,
790 : &s))
791 : {
792 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
793 : "fstat",
794 : filename);
795 0 : GNUNET_assert (0 == close (fd));
796 0 : return NULL;
797 : }
798 0 : resp = MHD_create_response_from_fd (s.st_size,
799 : fd);
800 0 : TALER_MHD_add_global_headers (resp,
801 : false);
802 0 : GNUNET_break (MHD_YES ==
803 : MHD_add_response_header (resp,
804 : MHD_HTTP_HEADER_CONTENT_TYPE,
805 : "application/pdf"));
806 0 : return resp;
807 : }
|