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 "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 : if (0 != unlink (stage->filename))
239 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
240 : "unlink",
241 : stage->filename);
242 0 : GNUNET_free (b);
243 0 : GNUNET_free (stage->filename);
244 0 : return false;
245 : }
246 :
247 : {
248 0 : size_t off = 0;
249 :
250 0 : while (off < n)
251 : {
252 : ssize_t r;
253 :
254 0 : r = write (fd,
255 0 : b + off,
256 : n - off);
257 0 : if (-1 == r)
258 : {
259 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
260 : "write",
261 : stage->filename);
262 0 : GNUNET_break (0 == close (fd));
263 0 : GNUNET_free (b);
264 0 : GNUNET_free (stage->filename);
265 0 : return false;
266 : }
267 0 : off += r;
268 : }
269 : }
270 0 : GNUNET_break (0 == close (fd));
271 0 : GNUNET_free (b);
272 0 : return true;
273 : }
274 :
275 :
276 : /**
277 : * Generate a response for @a tc indicating an error of type @a ec.
278 : *
279 : * @param[in,out] tc context to fail
280 : * @param ec error code to return
281 : * @param hint hint text to return
282 : */
283 : static void
284 0 : typst_context_fail (struct TALER_MHD_TypstContext *tc,
285 : enum TALER_ErrorCode ec,
286 : const char *hint)
287 : {
288 0 : struct TALER_MHD_TypstResponse resp = {
289 : .ec = ec,
290 : .details.hint = hint
291 : };
292 :
293 0 : if (NULL != tc->cb)
294 : {
295 0 : tc->cb (tc->cb_cls,
296 : &resp);
297 0 : tc->cb = NULL;
298 : }
299 0 : }
300 :
301 :
302 : /**
303 : * Helper task for typst_context_fail_async().
304 : *
305 : * @param cls a `struct TALER_MHD_TypstContext`
306 : */
307 : static void
308 0 : fail_async_cb (void *cls)
309 : {
310 0 : struct TALER_MHD_TypstContext *tc = cls;
311 :
312 0 : tc->t = NULL;
313 0 : typst_context_fail (tc,
314 : tc->async_ec,
315 0 : tc->async_hint);
316 0 : GNUNET_free (tc->async_hint);
317 0 : TALER_MHD_typst_cancel (tc);
318 0 : }
319 :
320 :
321 : /**
322 : * Generate a response for @a tc indicating an error of type @a ec.
323 : *
324 : * @param[in,out] tc context to fail
325 : * @param ec error code to return
326 : * @param hint hint text to return
327 : */
328 : static void
329 0 : typst_context_fail_async (struct TALER_MHD_TypstContext *tc,
330 : enum TALER_ErrorCode ec,
331 : const char *hint)
332 : {
333 0 : tc->async_ec = ec;
334 0 : tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint);
335 0 : tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb,
336 : tc);
337 0 : }
338 :
339 :
340 : /**
341 : * Called when the pdftk helper exited.
342 : *
343 : * @param cls our `struct TALER_MHD_TypstContext *`
344 : * @param type type of the process
345 : * @param exit_code status code of the process
346 : */
347 : static void
348 0 : pdftk_done_cb (void *cls,
349 : enum GNUNET_OS_ProcessStatusType type,
350 : long unsigned int exit_code)
351 : {
352 0 : struct TALER_MHD_TypstContext *tc = cls;
353 :
354 0 : tc->cwh = NULL;
355 0 : GNUNET_process_destroy (tc->proc);
356 0 : tc->proc = NULL;
357 0 : switch (type)
358 : {
359 0 : case GNUNET_OS_PROCESS_UNKNOWN:
360 0 : GNUNET_assert (0);
361 : return;
362 0 : case GNUNET_OS_PROCESS_RUNNING:
363 : /* we should not get this notification */
364 0 : GNUNET_break (0);
365 0 : typst_context_fail (tc,
366 : TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
367 : "pdftk in unexpected state");
368 0 : break;
369 0 : case GNUNET_OS_PROCESS_STOPPED:
370 : /* Someone is SIGSTOPing our helper!? */
371 0 : GNUNET_break (0);
372 0 : typst_context_fail (tc,
373 : TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
374 : "pdftk was stopped");
375 0 : break;
376 0 : case GNUNET_OS_PROCESS_EXITED:
377 0 : if (0 != exit_code)
378 : {
379 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
380 : "pdftk exited with status %d\n",
381 : (int) exit_code);
382 0 : typst_context_fail (tc,
383 : TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE,
384 : "pdftk failed");
385 : }
386 : else
387 : {
388 0 : struct TALER_MHD_TypstResponse resp = {
389 : .ec = TALER_EC_NONE,
390 0 : .details.filename = tc->output_file,
391 : };
392 :
393 0 : GNUNET_assert (NULL != tc->cb);
394 0 : tc->cb (tc->cb_cls,
395 : &resp);
396 0 : tc->cb = NULL;
397 : }
398 0 : break;
399 0 : case GNUNET_OS_PROCESS_SIGNALED:
400 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
401 : "pdftk died with signal %d\n",
402 : (int) exit_code);
403 0 : typst_context_fail (tc,
404 : TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
405 : "pdftk killed by signal");
406 0 : break;
407 : }
408 0 : TALER_MHD_typst_cancel (tc);
409 : }
410 :
411 :
412 : /**
413 : * Function called once all of the individual stages are done.
414 : * Triggers the pdftk run for @a tc.
415 : *
416 : * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for
417 : */
418 : static void
419 0 : complete_response (void *cls)
420 0 : {
421 0 : struct TALER_MHD_TypstContext *tc = cls;
422 0 : const char *argv[tc->num_stages + 5];
423 :
424 0 : tc->t = NULL;
425 0 : argv[0] = "pdftk";
426 0 : for (unsigned int i = 0; i<tc->num_stages; i++)
427 0 : argv[i + 1] = tc->stages[i].filename;
428 0 : argv[tc->num_stages + 1] = "cat";
429 0 : argv[tc->num_stages + 2] = "output";
430 0 : argv[tc->num_stages + 3] = tc->output_file;
431 0 : argv[tc->num_stages + 4] = NULL;
432 0 : tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
433 0 : if (GNUNET_OK !=
434 0 : GNUNET_process_run_command_argv (tc->proc,
435 : argv[0],
436 : argv))
437 : {
438 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
439 : "fork");
440 0 : GNUNET_process_destroy (tc->proc);
441 0 : tc->proc = NULL;
442 0 : TALER_MHD_typst_cancel (tc);
443 0 : return;
444 : }
445 0 : tc->cwh = GNUNET_wait_child (tc->proc,
446 : &pdftk_done_cb,
447 : tc);
448 0 : GNUNET_assert (NULL != tc->cwh);
449 : }
450 :
451 :
452 : /**
453 : * Cancel typst. Wrapper task to do so asynchronously.
454 : *
455 : * @param[in] cls a `struct TALER_MHD_TypstContext`
456 : */
457 : static void
458 0 : cancel_async (void *cls)
459 : {
460 0 : struct TALER_MHD_TypstContext *tc = cls;
461 :
462 0 : tc->t = NULL;
463 0 : TALER_MHD_typst_cancel (tc);
464 0 : }
465 :
466 :
467 : /**
468 : * Fail the typst context @a tc and clean it up asynchronously.
469 : *
470 : * Note that several stages may fail within the same SIGCHLD batch, in
471 : * which case the clean-up task will already have been scheduled by the
472 : * first failure; we must not schedule it a second time (and must not
473 : * clean up synchronously, as we are called from the child-death
474 : * notification).
475 : *
476 : * @param[in,out] tc context to fail
477 : * @param ec error code to return to the client
478 : * @param hint hint text to return to the client
479 : */
480 : static void
481 0 : stage_fail (struct TALER_MHD_TypstContext *tc,
482 : enum TALER_ErrorCode ec,
483 : const char *hint)
484 : {
485 0 : typst_context_fail (tc,
486 : ec,
487 : hint);
488 0 : if (NULL == tc->t)
489 0 : tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
490 : tc);
491 0 : }
492 :
493 :
494 : /**
495 : * Called when a typst helper exited.
496 : *
497 : * @param cls our `struct TypstStage *`
498 : * @param type type of the process
499 : * @param exit_code status code of the process
500 : */
501 : static void
502 0 : typst_done_cb (void *cls,
503 : enum GNUNET_OS_ProcessStatusType type,
504 : long unsigned int exit_code)
505 : {
506 0 : struct TypstStage *stage = cls;
507 0 : struct TALER_MHD_TypstContext *tc = stage->tc;
508 :
509 0 : stage->cwh = NULL;
510 0 : GNUNET_process_destroy (stage->proc);
511 0 : stage->proc = NULL;
512 0 : switch (type)
513 : {
514 0 : case GNUNET_OS_PROCESS_UNKNOWN:
515 0 : GNUNET_assert (0);
516 : return;
517 0 : case GNUNET_OS_PROCESS_RUNNING:
518 : /* we should not get this notification */
519 0 : GNUNET_break (0);
520 0 : stage_fail (tc,
521 : TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
522 : "Typst in unexpected state");
523 0 : return;
524 0 : case GNUNET_OS_PROCESS_STOPPED:
525 : /* Someone is SIGSTOPing our helper!? */
526 0 : GNUNET_break (0);
527 0 : stage_fail (tc,
528 : TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
529 : "Typst was stopped");
530 0 : return;
531 0 : case GNUNET_OS_PROCESS_EXITED:
532 0 : if (0 != exit_code)
533 : {
534 : char err[128];
535 :
536 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
537 : "typst exited with status %d\n",
538 : (int) exit_code);
539 0 : GNUNET_snprintf (err,
540 : sizeof (err),
541 : "Typst exited with status %d",
542 : (int) exit_code);
543 0 : stage_fail (tc,
544 : TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
545 : err);
546 0 : return;
547 : }
548 0 : break;
549 0 : case GNUNET_OS_PROCESS_SIGNALED:
550 : {
551 : char err[128];
552 :
553 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
554 : "typst died with signal %d\n",
555 : (int) exit_code);
556 0 : GNUNET_snprintf (err,
557 : sizeof (err),
558 : "Typst died with signal %d",
559 : (int) exit_code);
560 0 : stage_fail (tc,
561 : TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
562 : err);
563 0 : return;
564 : }
565 : break;
566 : }
567 0 : tc->active_stages--;
568 0 : if (NULL != stage->proc)
569 : {
570 0 : GNUNET_process_destroy (stage->proc);
571 0 : stage->proc = NULL;
572 : }
573 0 : if (0 != tc->active_stages)
574 0 : return;
575 0 : if (NULL != tc->t)
576 0 : return; /* clean-up of a failed stage is already scheduled */
577 0 : tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
578 : tc);
579 : }
580 :
581 :
582 : /**
583 : * Check that @a id only consists of characters that are safe to
584 : * interpolate into a Typst import statement (alphanumerics, dots,
585 : * hyphens and underscores). This prevents code/path injection via the form
586 : * name or version.
587 : *
588 : * @param id identifier to validate
589 : * @return true if @a id is non-empty and only uses the safe charset
590 : */
591 : static bool
592 0 : is_valid_form_identifier (const char *id)
593 : {
594 0 : if ( (NULL == id) ||
595 0 : ('\0' == id[0]) )
596 0 : return false;
597 0 : for (const char *p = id; '\0' != *p; p++)
598 : {
599 0 : char c = *p;
600 :
601 0 : if (! ( ( ('a' <= c) && ('z' >= c)) ||
602 0 : ( ('A' <= c) && ('Z' >= c)) ||
603 0 : ( ('0' <= c) && ('9' >= c)) ||
604 0 : ('.' == c) ||
605 : ('-' == c) ||
606 : ('_' == c) ) )
607 0 : return false;
608 : }
609 0 : return true;
610 : }
611 :
612 :
613 : /**
614 : * Setup typst stage to produce one of the PDF inputs.
615 : *
616 : * @param[out] stage initialized stage data
617 : * @param i index of the stage
618 : * @param tmpdir where to place temporary files
619 : * @param template_path where to find templates
620 : * @param doc input document specification
621 : * @return true on success
622 : */
623 : static bool
624 0 : setup_stage (struct TypstStage *stage,
625 : unsigned int i,
626 : const char *tmpdir,
627 : const char *template_path,
628 : const struct TALER_MHD_TypstDocument *doc)
629 : {
630 0 : struct TALER_MHD_TypstContext *tc = stage->tc;
631 : char *input;
632 : bool is_dot_typ;
633 :
634 0 : if (NULL == doc->form_name)
635 : {
636 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
637 : "Stage %u: Dumping inlined PDF attachment\n",
638 : i);
639 0 : return inline_pdf_stage (stage,
640 : tmpdir,
641 0 : doc->data);
642 : }
643 :
644 0 : if (! is_valid_form_identifier (doc->form_name))
645 : {
646 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
647 : "Stage %u: Invalid form name `%s'\n",
648 : i,
649 : doc->form_name);
650 0 : return false;
651 : }
652 0 : if ( (NULL != doc->form_version) &&
653 0 : (! is_valid_form_identifier (doc->form_version)) )
654 : {
655 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
656 : "Stage %u: Invalid form version `%s'\n",
657 : i,
658 : doc->form_version);
659 0 : return false;
660 : }
661 :
662 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
663 : "Stage %u: Handling form %s\n",
664 : i,
665 : doc->form_name);
666 :
667 : /* Setup inputs */
668 : {
669 : char *dirname;
670 :
671 0 : GNUNET_asprintf (&dirname,
672 : "%s/%u/",
673 : tmpdir,
674 : i);
675 0 : if (GNUNET_OK !=
676 0 : GNUNET_DISK_directory_create (dirname))
677 : {
678 0 : GNUNET_free (dirname);
679 0 : return false;
680 : }
681 0 : GNUNET_free (dirname);
682 : }
683 :
684 : /* Setup data input */
685 : {
686 : char *jfn;
687 :
688 0 : GNUNET_asprintf (&jfn,
689 : "%s/%u/input.json",
690 : tmpdir,
691 : i);
692 0 : if (0 !=
693 0 : json_dump_file (doc->data,
694 : jfn,
695 : JSON_INDENT (2)))
696 : {
697 0 : GNUNET_break (0);
698 0 : GNUNET_free (jfn);
699 0 : return false;
700 : }
701 0 : GNUNET_free (jfn);
702 : }
703 :
704 : /* setup output file name */
705 0 : GNUNET_asprintf (&stage->filename,
706 : "%s/%u/input.pdf",
707 : tmpdir,
708 : i);
709 :
710 : /* setup main input Typst file */
711 : {
712 : char *intyp;
713 0 : size_t slen = strlen (doc->form_name);
714 :
715 0 : is_dot_typ = ( (slen > 4) &&
716 0 : (0 == memcmp (&doc->form_name[slen - 4],
717 : ".typ",
718 : 4)) );
719 : /* We do not append the ":$VERSION" if a filename ending with ".typ"
720 : is given. Otherwise we append the version, or ":0.0.0" if no
721 : explicit version is given. */
722 0 : GNUNET_asprintf (&intyp,
723 : "#import \"%s/%s%s%s\": form\n"
724 : "#form(json(\"input.json\"))\n",
725 : template_path,
726 0 : doc->form_name,
727 : is_dot_typ ? "" : ":",
728 : is_dot_typ
729 : ? ""
730 0 : : ( (NULL == doc->form_version)
731 : ? "0.0.0"
732 0 : : doc->form_version));
733 0 : GNUNET_asprintf (&input,
734 : "%s/%u/input.typ",
735 : tmpdir,
736 : i);
737 0 : if (GNUNET_OK !=
738 0 : GNUNET_DISK_fn_write (input,
739 : intyp,
740 : strlen (intyp),
741 : GNUNET_DISK_PERM_USER_READ))
742 : {
743 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
744 : "write",
745 : input);
746 0 : GNUNET_free (input);
747 0 : GNUNET_free (intyp);
748 0 : return false;
749 : }
750 0 : GNUNET_free (intyp);
751 : }
752 :
753 : /* now setup typst invocation */
754 : {
755 : const char *argv[6];
756 :
757 0 : if (is_dot_typ)
758 : {
759 : /* This deliberately breaks the typst sandbox. Why? Because Typst sucks
760 : and does not support multiple roots, but here we have dynamic data in
761 : /tmp and a style file outside of /tmp (and copying is also not
762 : practical as we don't know what all to copy). Typst should really
763 : support multiple roots. Anyway, in production this path should not
764 : happen, because there we use Typst packages. */
765 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
766 : "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n",
767 : doc->form_name);
768 0 : argv[0] = "typst";
769 0 : argv[1] = "compile";
770 0 : argv[2] = "--root";
771 0 : argv[3] = "/";
772 0 : argv[4] = input;
773 0 : argv[5] = NULL;
774 : }
775 : else
776 : {
777 0 : argv[0] = "typst";
778 0 : argv[1] = "compile";
779 0 : argv[2] = input;
780 0 : argv[3] = NULL;
781 : }
782 0 : stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
783 : {
784 : char *datadir;
785 :
786 0 : datadir = GNUNET_OS_installation_get_path (
787 : tc->pd,
788 : GNUNET_OS_IPK_DATADIR);
789 0 : GNUNET_assert (GNUNET_OK ==
790 : GNUNET_process_set_options (
791 : stage->proc,
792 : GNUNET_process_option_set_environment ("XDG_DATA_HOME",
793 : datadir)));
794 0 : GNUNET_free (datadir);
795 : }
796 0 : if (GNUNET_OK !=
797 0 : GNUNET_process_run_command_argv (stage->proc,
798 : "typst",
799 : argv))
800 : {
801 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
802 : "fork");
803 0 : GNUNET_free (input);
804 0 : GNUNET_process_destroy (stage->proc);
805 0 : stage->proc = NULL;
806 0 : return false;
807 : }
808 0 : GNUNET_free (input);
809 0 : tc->active_stages++;
810 0 : stage->cwh = GNUNET_wait_child (stage->proc,
811 : &typst_done_cb,
812 : stage);
813 0 : GNUNET_assert (NULL != stage->cwh);
814 : }
815 0 : return true;
816 : }
817 :
818 :
819 : struct TALER_MHD_TypstContext *
820 1 : TALER_MHD_typst (
821 : const struct GNUNET_OS_ProjectData *pd,
822 : const struct GNUNET_CONFIGURATION_Handle *cfg,
823 : bool remove_on_exit,
824 : const char *cfg_section_name,
825 : unsigned int num_documents,
826 : const struct TALER_MHD_TypstDocument docs[static num_documents],
827 : TALER_MHD_TypstResultCallback cb,
828 : void *cb_cls)
829 1 : {
830 : static enum GNUNET_GenericReturnValue once = GNUNET_NO;
831 : struct TALER_MHD_TypstContext *tc;
832 :
833 1 : switch (once)
834 : {
835 0 : case GNUNET_OK:
836 0 : break;
837 1 : case GNUNET_NO:
838 1 : if (GNUNET_SYSERR ==
839 1 : GNUNET_OS_check_helper_binary ("typst",
840 : false,
841 : NULL))
842 : {
843 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
844 : "`typst' command not found\n");
845 1 : once = GNUNET_SYSERR;
846 1 : return NULL;
847 : }
848 0 : if (GNUNET_SYSERR ==
849 0 : GNUNET_OS_check_helper_binary ("pdftk",
850 : false,
851 : NULL))
852 : {
853 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
854 : "`pdftk' command not found\n");
855 0 : once = GNUNET_SYSERR;
856 0 : return NULL;
857 : }
858 0 : once = GNUNET_OK;
859 0 : break;
860 0 : case GNUNET_SYSERR:
861 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
862 : "PDF generation initialization failed before, not even trying again\n");
863 0 : return NULL;
864 : }
865 0 : tc = GNUNET_new (struct TALER_MHD_TypstContext);
866 0 : tc->pd = pd;
867 0 : tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
868 0 : tc->remove_on_exit = remove_on_exit;
869 0 : tc->cb = cb;
870 0 : tc->cb_cls = cb_cls;
871 0 : if (NULL == mkdtemp (tc->tmpdir))
872 : {
873 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
874 : "mkdtemp",
875 : tc->tmpdir);
876 0 : GNUNET_free (tc->tmpdir);
877 0 : TALER_MHD_typst_cancel (tc);
878 0 : return NULL;
879 : }
880 0 : GNUNET_asprintf (&tc->output_file,
881 : "%s/final.pdf",
882 : tc->tmpdir);
883 :
884 : /* setup typst stages */
885 : {
886 : char *template_path;
887 :
888 0 : if (GNUNET_OK !=
889 0 : GNUNET_CONFIGURATION_get_value_string (cfg,
890 : cfg_section_name,
891 : "TYPST_TEMPLATES",
892 : &template_path))
893 : {
894 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
895 : cfg_section_name,
896 : "TYPST_TEMPLATES");
897 0 : TALER_MHD_typst_cancel (tc);
898 0 : return NULL;
899 : }
900 0 : if ('@' != template_path[0])
901 0 : template_path = GNUNET_CONFIGURATION_expand_dollar (cfg,
902 : template_path);
903 0 : tc->stages = GNUNET_new_array (num_documents,
904 : struct TypstStage);
905 0 : tc->num_stages = num_documents;
906 0 : for (unsigned int i = 0; i<num_documents; i++)
907 : {
908 0 : tc->stages[i].tc = tc;
909 0 : if (! setup_stage (&tc->stages[i],
910 : i,
911 0 : tc->tmpdir,
912 : template_path,
913 0 : &docs[i]))
914 : {
915 : char err[128];
916 :
917 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
918 : "Typst setup failed on stage %u\n",
919 : i);
920 0 : GNUNET_snprintf (err,
921 : sizeof (err),
922 : "Typst setup failed on stage %u",
923 : i);
924 0 : typst_context_fail_async (tc,
925 : TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
926 : err);
927 0 : GNUNET_free (template_path);
928 0 : return tc;
929 : }
930 : }
931 0 : GNUNET_free (template_path);
932 : }
933 0 : if (0 == tc->active_stages)
934 : {
935 0 : tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
936 : tc);
937 : }
938 0 : return tc;
939 : }
940 :
941 :
942 : struct MHD_Response *
943 0 : TALER_MHD_response_from_pdf_file (const char *filename)
944 : {
945 : struct MHD_Response *resp;
946 : struct stat s;
947 : int fd;
948 :
949 0 : fd = open (filename,
950 : O_RDONLY);
951 0 : if (-1 == fd)
952 : {
953 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
954 : "open",
955 : filename);
956 0 : return NULL;
957 : }
958 0 : if (0 !=
959 0 : fstat (fd,
960 : &s))
961 : {
962 0 : GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
963 : "fstat",
964 : filename);
965 0 : GNUNET_assert (0 == close (fd));
966 0 : return NULL;
967 : }
968 0 : resp = MHD_create_response_from_fd (s.st_size,
969 : fd);
970 0 : if (NULL == resp)
971 : {
972 0 : GNUNET_break (0);
973 0 : GNUNET_assert (0 == close (fd));
974 0 : return NULL;
975 : }
976 0 : TALER_MHD_add_global_headers (resp,
977 : false);
978 0 : GNUNET_break (MHD_YES ==
979 : MHD_add_response_header (resp,
980 : MHD_HTTP_HEADER_CONTENT_TYPE,
981 : "application/pdf"));
982 0 : return resp;
983 : }
|