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 : * 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 0 : TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc)
129 : {
130 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
131 : "Cleaning up TypstContext\n");
132 0 : if (NULL != tc->t)
133 : {
134 0 : GNUNET_SCHEDULER_cancel (tc->t);
135 0 : tc->t = NULL;
136 : }
137 0 : for (unsigned int i = 0; i<tc->num_stages; i++)
138 : {
139 0 : struct TypstStage *stage = &tc->stages[i];
140 :
141 0 : if (NULL != stage->cwh)
142 : {
143 0 : GNUNET_wait_child_cancel (stage->cwh);
144 0 : stage->cwh = NULL;
145 : }
146 0 : if (NULL != stage->proc)
147 : {
148 0 : GNUNET_break (0 ==
149 : GNUNET_OS_process_kill (stage->proc,
150 : SIGKILL));
151 0 : GNUNET_OS_process_destroy (stage->proc);
152 0 : stage->proc = NULL;
153 : }
154 0 : GNUNET_free (stage->filename);
155 : }
156 0 : GNUNET_free (tc->stages);
157 0 : if (NULL != tc->cwh)
158 : {
159 0 : GNUNET_wait_child_cancel (tc->cwh);
160 0 : tc->cwh = NULL;
161 : }
162 0 : if (NULL != tc->proc)
163 : {
164 0 : GNUNET_break (0 ==
165 : GNUNET_OS_process_kill (tc->proc,
166 : SIGKILL));
167 0 : GNUNET_OS_process_destroy (tc->proc);
168 : }
169 0 : GNUNET_free (tc->output_file);
170 0 : if (NULL != tc->tmpdir)
171 : {
172 0 : if (tc->remove_on_exit)
173 0 : GNUNET_DISK_directory_remove (tc->tmpdir);
174 0 : GNUNET_free (tc->tmpdir);
175 : }
176 0 : GNUNET_free (tc);
177 0 : }
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 0 : inline_pdf_stage (struct TypstStage *stage,
190 : const char *tmpdir,
191 : const json_t *data)
192 : {
193 0 : const char *str = json_string_value (data);
194 : char *fn;
195 : size_t n;
196 : void *b;
197 : int fd;
198 :
199 0 : if (NULL == str)
200 : {
201 0 : GNUNET_break (0);
202 0 : return false;
203 : }
204 0 : b = NULL;
205 0 : n = GNUNET_STRINGS_base64_decode (str,
206 : strlen (str),
207 : &b);
208 0 : if (NULL == b)
209 : {
210 0 : GNUNET_break (0);
211 0 : return false;
212 : }
213 0 : GNUNET_asprintf (&fn,
214 : "%s/external-",
215 : tmpdir);
216 0 : stage->filename = GNUNET_DISK_mktemp (fn);
217 0 : if (NULL == stage->filename)
218 : {
219 0 : GNUNET_break (0);
220 0 : GNUNET_free (b);
221 0 : GNUNET_free (fn);
222 0 : return false;
223 : }
224 0 : GNUNET_free (fn);
225 0 : fd = open (stage->filename,
226 : O_WRONLY | O_TRUNC,
227 : S_IRUSR | S_IWUSR);
228 0 : if (-1 == fd)
229 : {
230 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
231 : "open",
232 : stage->filename);
233 0 : GNUNET_free (b);
234 0 : GNUNET_free (stage->filename);
235 0 : return false;
236 : }
237 :
238 : {
239 0 : size_t off = 0;
240 :
241 0 : while (off < n)
242 : {
243 : ssize_t r;
244 :
245 0 : r = write (fd,
246 0 : b + off,
247 : n - off);
248 0 : if (-1 == r)
249 : {
250 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
251 : "write",
252 : stage->filename);
253 0 : GNUNET_break (0 == close (fd));
254 0 : GNUNET_free (b);
255 0 : GNUNET_free (stage->filename);
256 0 : return false;
257 : }
258 0 : off += r;
259 : }
260 : }
261 0 : GNUNET_break (0 == close (fd));
262 0 : 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 0 : typst_context_fail (struct TALER_MHD_TypstContext *tc,
275 : enum TALER_ErrorCode ec,
276 : const char *hint)
277 : {
278 0 : struct TALER_MHD_TypstResponse resp = {
279 : .ec = ec,
280 : .details.hint = hint
281 : };
282 :
283 0 : if (NULL != tc->cb)
284 : {
285 0 : tc->cb (tc->cb_cls,
286 : &resp);
287 0 : tc->cb = NULL;
288 : }
289 0 : }
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 0 : fail_async_cb (void *cls)
299 : {
300 0 : struct TALER_MHD_TypstContext *tc = cls;
301 :
302 0 : tc->t = NULL;
303 0 : typst_context_fail (tc,
304 : tc->async_ec,
305 0 : tc->async_hint);
306 0 : GNUNET_free (tc->async_hint);
307 0 : TALER_MHD_typst_cancel (tc);
308 0 : }
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 0 : typst_context_fail_async (struct TALER_MHD_TypstContext *tc,
320 : enum TALER_ErrorCode ec,
321 : const char *hint)
322 : {
323 0 : tc->async_ec = ec;
324 0 : tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint);
325 0 : tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb,
326 : tc);
327 0 : }
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 0 : pdftk_done_cb (void *cls,
339 : enum GNUNET_OS_ProcessStatusType type,
340 : long unsigned int exit_code)
341 : {
342 0 : struct TALER_MHD_TypstContext *tc = cls;
343 :
344 0 : tc->cwh = NULL;
345 0 : GNUNET_OS_process_destroy (tc->proc);
346 0 : tc->proc = NULL;
347 0 : switch (type)
348 : {
349 0 : case GNUNET_OS_PROCESS_UNKNOWN:
350 0 : GNUNET_assert (0);
351 : return;
352 0 : case GNUNET_OS_PROCESS_RUNNING:
353 : /* we should not get this notification */
354 0 : GNUNET_break (0);
355 0 : return;
356 0 : case GNUNET_OS_PROCESS_STOPPED:
357 : /* Someone is SIGSTOPing our helper!? */
358 0 : GNUNET_break (0);
359 0 : return;
360 0 : case GNUNET_OS_PROCESS_EXITED:
361 0 : if (0 != exit_code)
362 : {
363 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
364 : "pdftk exited with status %d\n",
365 : (int) exit_code);
366 0 : typst_context_fail (tc,
367 : TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE,
368 : "pdftk failed");
369 : }
370 : else
371 : {
372 0 : struct TALER_MHD_TypstResponse resp = {
373 : .ec = TALER_EC_NONE,
374 0 : .details.filename = tc->output_file,
375 : };
376 :
377 0 : GNUNET_assert (NULL != tc->cb);
378 0 : tc->cb (tc->cb_cls,
379 : &resp);
380 0 : tc->cb = NULL;
381 : }
382 0 : break;
383 0 : case GNUNET_OS_PROCESS_SIGNALED:
384 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
385 : "pdftk died with signal %d\n",
386 : (int) exit_code);
387 0 : typst_context_fail (tc,
388 : TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
389 : "pdftk killed by signal");
390 0 : break;
391 : }
392 0 : 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 0 : complete_response (void *cls)
404 0 : {
405 0 : struct TALER_MHD_TypstContext *tc = cls;
406 0 : const char *argv[tc->num_stages + 5];
407 :
408 0 : tc->t = NULL;
409 0 : argv[0] = "pdftk";
410 0 : for (unsigned int i = 0; i<tc->num_stages; i++)
411 0 : argv[i + 1] = tc->stages[i].filename;
412 0 : argv[tc->num_stages + 1] = "cat";
413 0 : argv[tc->num_stages + 2] = "output";
414 0 : argv[tc->num_stages + 3] = tc->output_file;
415 0 : argv[tc->num_stages + 4] = NULL;
416 0 : 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 0 : if (NULL == tc->proc)
424 : {
425 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
426 : "fork");
427 0 : TALER_MHD_typst_cancel (tc);
428 0 : return;
429 : }
430 0 : tc->cwh = GNUNET_wait_child (tc->proc,
431 : &pdftk_done_cb,
432 : tc);
433 0 : 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 0 : cancel_async (void *cls)
444 : {
445 0 : struct TALER_MHD_TypstContext *tc = cls;
446 :
447 0 : tc->t = NULL;
448 0 : TALER_MHD_typst_cancel (tc);
449 0 : }
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 0 : typst_done_cb (void *cls,
461 : enum GNUNET_OS_ProcessStatusType type,
462 : long unsigned int exit_code)
463 : {
464 0 : struct TypstStage *stage = cls;
465 0 : struct TALER_MHD_TypstContext *tc = stage->tc;
466 :
467 0 : stage->cwh = NULL;
468 0 : GNUNET_OS_process_destroy (stage->proc);
469 0 : stage->proc = NULL;
470 0 : switch (type)
471 : {
472 0 : case GNUNET_OS_PROCESS_UNKNOWN:
473 0 : GNUNET_assert (0);
474 : return;
475 0 : case GNUNET_OS_PROCESS_RUNNING:
476 : /* we should not get this notification */
477 0 : GNUNET_break (0);
478 0 : return;
479 0 : case GNUNET_OS_PROCESS_STOPPED:
480 : /* Someone is SIGSTOPing our helper!? */
481 0 : GNUNET_break (0);
482 0 : return;
483 0 : case GNUNET_OS_PROCESS_EXITED:
484 0 : if (0 != exit_code)
485 : {
486 : char err[128];
487 :
488 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
489 : "typst exited with status %d\n",
490 : (int) exit_code);
491 0 : GNUNET_snprintf (err,
492 : sizeof (err),
493 : "Typst exited with status %d",
494 : (int) exit_code);
495 0 : typst_context_fail (tc,
496 : TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
497 : err);
498 0 : GNUNET_assert (NULL == tc->t);
499 0 : tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
500 : tc);
501 0 : return;
502 : }
503 0 : break;
504 0 : case GNUNET_OS_PROCESS_SIGNALED:
505 : {
506 : char err[128];
507 :
508 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
509 : "typst died with signal %d\n",
510 : (int) exit_code);
511 0 : GNUNET_snprintf (err,
512 : sizeof (err),
513 : "Typst died with signal %d",
514 : (int) exit_code);
515 0 : typst_context_fail (tc,
516 : TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
517 : err);
518 0 : GNUNET_assert (NULL == tc->t);
519 0 : tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
520 : tc);
521 0 : return;
522 : }
523 : break;
524 : }
525 0 : tc->active_stages--;
526 0 : if (NULL != stage->proc)
527 : {
528 0 : GNUNET_OS_process_destroy (stage->proc);
529 0 : stage->proc = NULL;
530 : }
531 0 : if (0 != tc->active_stages)
532 0 : return;
533 0 : GNUNET_assert (NULL == tc->t);
534 0 : 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 0 : 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 :
558 0 : if (NULL == doc->form_name)
559 : {
560 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
561 : "Stage %u: Dumping inlined PDF attachment\n",
562 : i);
563 0 : return inline_pdf_stage (stage,
564 : tmpdir,
565 0 : doc->data);
566 : }
567 :
568 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
569 : "Stage %u: Handling form %s\n",
570 : i,
571 : doc->form_name);
572 :
573 : /* Setup inputs */
574 : {
575 : char *dirname;
576 :
577 0 : GNUNET_asprintf (&dirname,
578 : "%s/%u/",
579 : tmpdir,
580 : i);
581 0 : if (GNUNET_OK !=
582 0 : GNUNET_DISK_directory_create (dirname))
583 : {
584 0 : GNUNET_free (dirname);
585 0 : return false;
586 : }
587 0 : GNUNET_free (dirname);
588 : }
589 :
590 : /* Setup data input */
591 : {
592 : char *jfn;
593 :
594 0 : GNUNET_asprintf (&jfn,
595 : "%s/%u/input.json",
596 : tmpdir,
597 : i);
598 0 : if (0 !=
599 0 : json_dump_file (doc->data,
600 : jfn,
601 : JSON_INDENT (2)))
602 : {
603 0 : GNUNET_break (0);
604 0 : GNUNET_free (jfn);
605 0 : return false;
606 : }
607 0 : GNUNET_free (jfn);
608 : }
609 :
610 : /* setup output file name */
611 0 : GNUNET_asprintf (&stage->filename,
612 : "%s/%u/input.pdf",
613 : tmpdir,
614 : i);
615 :
616 : /* setup main input Typst file */
617 : {
618 : char *intyp;
619 : char *template_fn;
620 :
621 0 : GNUNET_asprintf (&template_fn,
622 : "%s%s.typ",
623 : template_path,
624 0 : doc->form_name);
625 0 : if (GNUNET_YES !=
626 0 : GNUNET_DISK_file_test_read (template_fn))
627 : {
628 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
629 : "access",
630 : template_fn);
631 0 : GNUNET_free (template_fn);
632 0 : return false;
633 : }
634 0 : GNUNET_asprintf (&intyp,
635 : "#import \"%s\": form\n"
636 : "#form(json(\"%s/%u/input.json\"))\n",
637 : template_fn,
638 : tmpdir,
639 : i);
640 0 : GNUNET_asprintf (&input,
641 : "%s/%u/input.typ",
642 : tmpdir,
643 : i);
644 0 : if (GNUNET_OK !=
645 0 : GNUNET_DISK_fn_write (input,
646 : intyp,
647 : strlen (intyp),
648 : GNUNET_DISK_PERM_USER_READ))
649 : {
650 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
651 : "write",
652 : input);
653 0 : GNUNET_free (input);
654 0 : GNUNET_free (intyp);
655 0 : GNUNET_free (template_fn);
656 0 : return false;
657 : }
658 0 : GNUNET_free (template_fn);
659 0 : GNUNET_free (intyp);
660 : }
661 :
662 : /* now setup typst invocation */
663 : {
664 : const char *argv[6];
665 :
666 0 : argv[0] = "typst";
667 0 : argv[1] = "compile";
668 : /* This deliberately breaks the typst sandbox. Why? Because
669 : they suck and do not support multiple roots, but we have
670 : dynamic data in /tmp and resources outside of /tmp and
671 : copying all the time is also bad. Typst should really
672 : support multiple roots. */
673 0 : argv[2] = "--root";
674 0 : argv[3] = "/";
675 0 : argv[4] = input;
676 0 : argv[5] = NULL;
677 0 : stage->proc = GNUNET_OS_start_process_vap (
678 : GNUNET_OS_INHERIT_STD_ERR,
679 : NULL,
680 : NULL,
681 : NULL,
682 : "typst",
683 : (char **) argv);
684 0 : if (NULL == stage->proc)
685 : {
686 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
687 : "fork");
688 0 : GNUNET_free (input);
689 0 : return false;
690 : }
691 0 : GNUNET_free (input);
692 0 : stage->tc->active_stages++;
693 0 : stage->cwh = GNUNET_wait_child (stage->proc,
694 : &typst_done_cb,
695 : stage);
696 0 : GNUNET_assert (NULL != stage->cwh);
697 : }
698 0 : return true;
699 : }
700 :
701 :
702 : struct TALER_MHD_TypstContext *
703 1 : TALER_MHD_typst (
704 : const struct GNUNET_CONFIGURATION_Handle *cfg,
705 : bool remove_on_exit,
706 : const char *cfg_section_name,
707 : unsigned int num_documents,
708 : const struct TALER_MHD_TypstDocument docs[static num_documents],
709 : TALER_MHD_TypstResultCallback cb,
710 : void *cb_cls)
711 1 : {
712 : static enum GNUNET_GenericReturnValue once = GNUNET_NO;
713 : struct TALER_MHD_TypstContext *tc;
714 :
715 1 : switch (once)
716 : {
717 0 : case GNUNET_OK:
718 0 : break;
719 1 : case GNUNET_NO:
720 1 : if (GNUNET_SYSERR ==
721 1 : GNUNET_OS_check_helper_binary ("typst",
722 : false,
723 : NULL))
724 : {
725 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
726 : "`typst' command not found\n");
727 1 : once = GNUNET_SYSERR;
728 1 : return NULL;
729 : }
730 0 : if (GNUNET_SYSERR ==
731 0 : GNUNET_OS_check_helper_binary ("pdftk",
732 : false,
733 : NULL))
734 : {
735 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
736 : "`pdftk' command not found\n");
737 0 : once = GNUNET_SYSERR;
738 0 : return NULL;
739 : }
740 0 : once = GNUNET_OK;
741 0 : break;
742 0 : case GNUNET_SYSERR:
743 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
744 : "PDF generation initialization failed before, not even trying again\n");
745 0 : return NULL;
746 : }
747 0 : tc = GNUNET_new (struct TALER_MHD_TypstContext);
748 0 : tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
749 0 : tc->remove_on_exit = remove_on_exit;
750 0 : tc->cb = cb;
751 0 : tc->cb_cls = cb_cls;
752 0 : if (NULL == mkdtemp (tc->tmpdir))
753 : {
754 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
755 : "mkdtemp",
756 : tc->tmpdir);
757 0 : GNUNET_free (tc->tmpdir);
758 0 : TALER_MHD_typst_cancel (tc);
759 0 : return NULL;
760 : }
761 0 : GNUNET_asprintf (&tc->output_file,
762 : "%s/final.pdf",
763 : tc->tmpdir);
764 :
765 : /* setup typst stages */
766 : {
767 : char *template_path;
768 :
769 0 : if (GNUNET_OK !=
770 0 : GNUNET_CONFIGURATION_get_value_filename (cfg,
771 : cfg_section_name,
772 : "TYPST_TEMPLATES",
773 : &template_path))
774 : {
775 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
776 : cfg_section_name,
777 : "TYPST_TEMPLATES");
778 0 : TALER_MHD_typst_cancel (tc);
779 0 : return NULL;
780 : }
781 0 : tc->stages = GNUNET_new_array (num_documents,
782 : struct TypstStage);
783 0 : tc->num_stages = num_documents;
784 0 : for (unsigned int i = 0; i<num_documents; i++)
785 : {
786 0 : tc->stages[i].tc = tc;
787 0 : if (! setup_stage (&tc->stages[i],
788 : i,
789 0 : tc->tmpdir,
790 : template_path,
791 0 : &docs[i]))
792 : {
793 : char err[128];
794 :
795 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
796 : "Typst setup failed on stage %u\n",
797 : i);
798 0 : GNUNET_snprintf (err,
799 : sizeof (err),
800 : "Typst setup failed on stage %u",
801 : i);
802 0 : typst_context_fail_async (tc,
803 : TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
804 : err);
805 0 : return tc;
806 : }
807 : }
808 0 : GNUNET_free (template_path);
809 : }
810 0 : if (0 == tc->active_stages)
811 : {
812 0 : tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
813 : tc);
814 : }
815 0 : return tc;
816 : }
817 :
818 :
819 : struct MHD_Response *
820 0 : TALER_MHD_response_from_pdf_file (const char *filename)
821 : {
822 : struct MHD_Response *resp;
823 : struct stat s;
824 : int fd;
825 :
826 0 : fd = open (filename,
827 : O_RDONLY);
828 0 : if (-1 == fd)
829 : {
830 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
831 : "open",
832 : filename);
833 0 : return NULL;
834 : }
835 0 : if (0 !=
836 0 : fstat (fd,
837 : &s))
838 : {
839 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
840 : "fstat",
841 : filename);
842 0 : GNUNET_assert (0 == close (fd));
843 0 : return NULL;
844 : }
845 0 : resp = MHD_create_response_from_fd (s.st_size,
846 : fd);
847 0 : TALER_MHD_add_global_headers (resp,
848 : false);
849 0 : GNUNET_break (MHD_YES ==
850 : MHD_add_response_header (resp,
851 : MHD_HTTP_HEADER_CONTENT_TYPE,
852 : "application/pdf"));
853 0 : return resp;
854 : }
|