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