exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

mhd_typst.c (25644B)


      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 TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc)
    134 {
    135   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    136               "Cleaning up TypstContext\n");
    137   if (NULL != tc->t)
    138   {
    139     GNUNET_SCHEDULER_cancel (tc->t);
    140     tc->t = NULL;
    141   }
    142   for (unsigned int i = 0; i<tc->num_stages; i++)
    143   {
    144     struct TypstStage *stage = &tc->stages[i];
    145 
    146     if (NULL != stage->cwh)
    147     {
    148       GNUNET_wait_child_cancel (stage->cwh);
    149       stage->cwh = NULL;
    150     }
    151     if (NULL != stage->proc)
    152     {
    153       GNUNET_break (GNUNET_OK ==
    154                     GNUNET_process_kill (stage->proc,
    155                                          SIGKILL));
    156       GNUNET_process_destroy (stage->proc);
    157       stage->proc = NULL;
    158     }
    159     GNUNET_free (stage->filename);
    160   }
    161   GNUNET_free (tc->stages);
    162   if (NULL != tc->cwh)
    163   {
    164     GNUNET_wait_child_cancel (tc->cwh);
    165     tc->cwh = NULL;
    166   }
    167   if (NULL != tc->proc)
    168   {
    169     GNUNET_break (GNUNET_OK ==
    170                   GNUNET_process_kill (tc->proc,
    171                                        SIGKILL));
    172     GNUNET_process_destroy (tc->proc);
    173   }
    174   GNUNET_free (tc->output_file);
    175   if (NULL != tc->tmpdir)
    176   {
    177     if (tc->remove_on_exit)
    178       GNUNET_DISK_directory_remove (tc->tmpdir);
    179     GNUNET_free (tc->tmpdir);
    180   }
    181   GNUNET_free (tc);
    182 }
    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 inline_pdf_stage (struct TypstStage *stage,
    195                   const char *tmpdir,
    196                   const json_t *data)
    197 {
    198   const char *str = json_string_value (data);
    199   char *fn;
    200   size_t n;
    201   void *b;
    202   int fd;
    203 
    204   if (NULL == str)
    205   {
    206     GNUNET_break (0);
    207     return false;
    208   }
    209   b = NULL;
    210   n = GNUNET_STRINGS_base64_decode (str,
    211                                     strlen (str),
    212                                     &b);
    213   if (NULL == b)
    214   {
    215     GNUNET_break (0);
    216     return false;
    217   }
    218   GNUNET_asprintf (&fn,
    219                    "%s/external-",
    220                    tmpdir);
    221   stage->filename = GNUNET_DISK_mktemp (fn);
    222   if (NULL == stage->filename)
    223   {
    224     GNUNET_break (0);
    225     GNUNET_free (b);
    226     GNUNET_free (fn);
    227     return false;
    228   }
    229   GNUNET_free (fn);
    230   fd = open (stage->filename,
    231              O_WRONLY | O_TRUNC,
    232              S_IRUSR | S_IWUSR);
    233   if (-1 == fd)
    234   {
    235     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    236                               "open",
    237                               stage->filename);
    238     if (0 != unlink (stage->filename))
    239       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
    240                                 "unlink",
    241                                 stage->filename);
    242     GNUNET_free (b);
    243     GNUNET_free (stage->filename);
    244     return false;
    245   }
    246 
    247   {
    248     size_t off = 0;
    249 
    250     while (off < n)
    251     {
    252       ssize_t r;
    253 
    254       r = write (fd,
    255                  b + off,
    256                  n - off);
    257       if (-1 == r)
    258       {
    259         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    260                                   "write",
    261                                   stage->filename);
    262         GNUNET_break (0 == close (fd));
    263         GNUNET_free (b);
    264         GNUNET_free (stage->filename);
    265         return false;
    266       }
    267       off += r;
    268     }
    269   }
    270   GNUNET_break (0 == close (fd));
    271   GNUNET_free (b);
    272   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 typst_context_fail (struct TALER_MHD_TypstContext *tc,
    285                     enum TALER_ErrorCode ec,
    286                     const char *hint)
    287 {
    288   struct TALER_MHD_TypstResponse resp = {
    289     .ec = ec,
    290     .details.hint = hint
    291   };
    292 
    293   if (NULL != tc->cb)
    294   {
    295     tc->cb (tc->cb_cls,
    296             &resp);
    297     tc->cb = NULL;
    298   }
    299 }
    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 fail_async_cb (void *cls)
    309 {
    310   struct TALER_MHD_TypstContext *tc = cls;
    311 
    312   tc->t = NULL;
    313   typst_context_fail (tc,
    314                       tc->async_ec,
    315                       tc->async_hint);
    316   GNUNET_free (tc->async_hint);
    317   TALER_MHD_typst_cancel (tc);
    318 }
    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 typst_context_fail_async (struct TALER_MHD_TypstContext *tc,
    330                           enum TALER_ErrorCode ec,
    331                           const char *hint)
    332 {
    333   tc->async_ec = ec;
    334   tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint);
    335   tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb,
    336                                     tc);
    337 }
    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 pdftk_done_cb (void *cls,
    349                enum GNUNET_OS_ProcessStatusType type,
    350                long unsigned int exit_code)
    351 {
    352   struct TALER_MHD_TypstContext *tc = cls;
    353 
    354   tc->cwh = NULL;
    355   GNUNET_process_destroy (tc->proc);
    356   tc->proc = NULL;
    357   switch (type)
    358   {
    359   case GNUNET_OS_PROCESS_UNKNOWN:
    360     GNUNET_assert (0);
    361     return;
    362   case GNUNET_OS_PROCESS_RUNNING:
    363     /* we should not get this notification */
    364     GNUNET_break (0);
    365     typst_context_fail (tc,
    366                         TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
    367                         "pdftk in unexpected state");
    368     break;
    369   case GNUNET_OS_PROCESS_STOPPED:
    370     /* Someone is SIGSTOPing our helper!? */
    371     GNUNET_break (0);
    372     typst_context_fail (tc,
    373                         TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
    374                         "pdftk was stopped");
    375     break;
    376   case GNUNET_OS_PROCESS_EXITED:
    377     if (0 != exit_code)
    378     {
    379       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    380                   "pdftk exited with status %d\n",
    381                   (int) exit_code);
    382       typst_context_fail (tc,
    383                           TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE,
    384                           "pdftk failed");
    385     }
    386     else
    387     {
    388       struct TALER_MHD_TypstResponse resp = {
    389         .ec = TALER_EC_NONE,
    390         .details.filename = tc->output_file,
    391       };
    392 
    393       GNUNET_assert (NULL != tc->cb);
    394       tc->cb (tc->cb_cls,
    395               &resp);
    396       tc->cb = NULL;
    397     }
    398     break;
    399   case GNUNET_OS_PROCESS_SIGNALED:
    400     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    401                 "pdftk died with signal %d\n",
    402                 (int) exit_code);
    403     typst_context_fail (tc,
    404                         TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
    405                         "pdftk killed by signal");
    406     break;
    407   }
    408   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 complete_response (void *cls)
    420 {
    421   struct TALER_MHD_TypstContext *tc = cls;
    422   const char *argv[tc->num_stages + 5];
    423 
    424   tc->t = NULL;
    425   argv[0] = "pdftk";
    426   for (unsigned int i = 0; i<tc->num_stages; i++)
    427     argv[i + 1] = tc->stages[i].filename;
    428   argv[tc->num_stages + 1] = "cat";
    429   argv[tc->num_stages + 2] = "output";
    430   argv[tc->num_stages + 3] = tc->output_file;
    431   argv[tc->num_stages + 4] = NULL;
    432   tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    433   if (GNUNET_OK !=
    434       GNUNET_process_run_command_argv (tc->proc,
    435                                        argv[0],
    436                                        argv))
    437   {
    438     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    439                          "fork");
    440     GNUNET_process_destroy (tc->proc);
    441     tc->proc = NULL;
    442     TALER_MHD_typst_cancel (tc);
    443     return;
    444   }
    445   tc->cwh = GNUNET_wait_child (tc->proc,
    446                                &pdftk_done_cb,
    447                                tc);
    448   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 cancel_async (void *cls)
    459 {
    460   struct TALER_MHD_TypstContext *tc = cls;
    461 
    462   tc->t = NULL;
    463   TALER_MHD_typst_cancel (tc);
    464 }
    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 stage_fail (struct TALER_MHD_TypstContext *tc,
    482             enum TALER_ErrorCode ec,
    483             const char *hint)
    484 {
    485   typst_context_fail (tc,
    486                       ec,
    487                       hint);
    488   if (NULL == tc->t)
    489     tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
    490                                       tc);
    491 }
    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 typst_done_cb (void *cls,
    503                enum GNUNET_OS_ProcessStatusType type,
    504                long unsigned int exit_code)
    505 {
    506   struct TypstStage *stage = cls;
    507   struct TALER_MHD_TypstContext *tc = stage->tc;
    508 
    509   stage->cwh = NULL;
    510   GNUNET_process_destroy (stage->proc);
    511   stage->proc = NULL;
    512   switch (type)
    513   {
    514   case GNUNET_OS_PROCESS_UNKNOWN:
    515     GNUNET_assert (0);
    516     return;
    517   case GNUNET_OS_PROCESS_RUNNING:
    518     /* we should not get this notification */
    519     GNUNET_break (0);
    520     stage_fail (tc,
    521                 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
    522                 "Typst in unexpected state");
    523     return;
    524   case GNUNET_OS_PROCESS_STOPPED:
    525     /* Someone is SIGSTOPing our helper!? */
    526     GNUNET_break (0);
    527     stage_fail (tc,
    528                 TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
    529                 "Typst was stopped");
    530     return;
    531   case GNUNET_OS_PROCESS_EXITED:
    532     if (0 != exit_code)
    533     {
    534       char err[128];
    535 
    536       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    537                   "typst exited with status %d\n",
    538                   (int) exit_code);
    539       GNUNET_snprintf (err,
    540                        sizeof (err),
    541                        "Typst exited with status %d",
    542                        (int) exit_code);
    543       stage_fail (tc,
    544                   TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
    545                   err);
    546       return;
    547     }
    548     break;
    549   case GNUNET_OS_PROCESS_SIGNALED:
    550     {
    551       char err[128];
    552 
    553       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    554                   "typst died with signal %d\n",
    555                   (int) exit_code);
    556       GNUNET_snprintf (err,
    557                        sizeof (err),
    558                        "Typst died with signal %d",
    559                        (int) exit_code);
    560       stage_fail (tc,
    561                   TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
    562                   err);
    563       return;
    564     }
    565     break;
    566   }
    567   tc->active_stages--;
    568   if (NULL != stage->proc)
    569   {
    570     GNUNET_process_destroy (stage->proc);
    571     stage->proc = NULL;
    572   }
    573   if (0 != tc->active_stages)
    574     return;
    575   if (NULL != tc->t)
    576     return; /* clean-up of a failed stage is already scheduled */
    577   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 and
    585  * `.`, `-`, `_`).  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 is_valid_form_identifier (const char *id)
    593 {
    594   if ( (NULL == id) ||
    595        ('\0' == id[0]) )
    596     return false;
    597   for (const char *p = id; '\0' != *p; p++)
    598   {
    599     char c = *p;
    600 
    601     if (! ( ( ('a' <= c) && ('z' >= c)) ||
    602             ( ('A' <= c) && ('Z' >= c)) ||
    603             ( ('0' <= c) && ('9' >= c)) ||
    604             ('.' == c) ||
    605             ('-' == c) ||
    606             ('_' == c) ) )
    607       return false;
    608   }
    609   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 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   struct TALER_MHD_TypstContext *tc = stage->tc;
    631   char *input;
    632   bool is_dot_typ;
    633 
    634   if (NULL == doc->form_name)
    635   {
    636     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    637                 "Stage %u: Dumping inlined PDF attachment\n",
    638                 i);
    639     return inline_pdf_stage (stage,
    640                              tmpdir,
    641                              doc->data);
    642   }
    643 
    644   if (! is_valid_form_identifier (doc->form_name))
    645   {
    646     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    647                 "Stage %u: Invalid form name `%s'\n",
    648                 i,
    649                 doc->form_name);
    650     return false;
    651   }
    652   if ( (NULL != doc->form_version) &&
    653        (! is_valid_form_identifier (doc->form_version)) )
    654   {
    655     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    656                 "Stage %u: Invalid form version `%s'\n",
    657                 i,
    658                 doc->form_version);
    659     return false;
    660   }
    661 
    662   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     GNUNET_asprintf (&dirname,
    672                      "%s/%u/",
    673                      tmpdir,
    674                      i);
    675     if (GNUNET_OK !=
    676         GNUNET_DISK_directory_create (dirname))
    677     {
    678       GNUNET_free (dirname);
    679       return false;
    680     }
    681     GNUNET_free (dirname);
    682   }
    683 
    684   /* Setup data input */
    685   {
    686     char *jfn;
    687 
    688     GNUNET_asprintf (&jfn,
    689                      "%s/%u/input.json",
    690                      tmpdir,
    691                      i);
    692     if (0 !=
    693         json_dump_file (doc->data,
    694                         jfn,
    695                         JSON_INDENT (2)))
    696     {
    697       GNUNET_break (0);
    698       GNUNET_free (jfn);
    699       return false;
    700     }
    701     GNUNET_free (jfn);
    702   }
    703 
    704   /* setup output file name */
    705   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     size_t slen = strlen (doc->form_name);
    714 
    715     is_dot_typ = ( (slen > 4) &&
    716                    (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     GNUNET_asprintf (&intyp,
    723                      "#import \"%s/%s%s%s\": form\n"
    724                      "#form(json(\"input.json\"))\n",
    725                      template_path,
    726                      doc->form_name,
    727                      is_dot_typ ? "" : ":",
    728                      is_dot_typ
    729                      ? ""
    730                      : ( (NULL == doc->form_version)
    731                          ? "0.0.0"
    732                          : doc->form_version));
    733     GNUNET_asprintf (&input,
    734                      "%s/%u/input.typ",
    735                      tmpdir,
    736                      i);
    737     if (GNUNET_OK !=
    738         GNUNET_DISK_fn_write (input,
    739                               intyp,
    740                               strlen (intyp),
    741                               GNUNET_DISK_PERM_USER_READ))
    742     {
    743       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    744                                 "write",
    745                                 input);
    746       GNUNET_free (input);
    747       GNUNET_free (intyp);
    748       return false;
    749     }
    750     GNUNET_free (intyp);
    751   }
    752 
    753   /* now setup typst invocation */
    754   {
    755     const char *argv[6];
    756 
    757     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       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    766                   "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n",
    767                   doc->form_name);
    768       argv[0] = "typst";
    769       argv[1] = "compile";
    770       argv[2] = "--root";
    771       argv[3] = "/";
    772       argv[4] = input;
    773       argv[5] = NULL;
    774     }
    775     else
    776     {
    777       argv[0] = "typst";
    778       argv[1] = "compile";
    779       argv[2] = input;
    780       argv[3] = NULL;
    781     }
    782     stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    783     {
    784       char *datadir;
    785 
    786       datadir = GNUNET_OS_installation_get_path (
    787         tc->pd,
    788         GNUNET_OS_IPK_DATADIR);
    789       GNUNET_assert (GNUNET_OK ==
    790                      GNUNET_process_set_options (
    791                        stage->proc,
    792                        GNUNET_process_option_set_environment ("XDG_DATA_HOME",
    793                                                               datadir)));
    794       GNUNET_free (datadir);
    795     }
    796     if (GNUNET_OK !=
    797         GNUNET_process_run_command_argv (stage->proc,
    798                                          "typst",
    799                                          argv))
    800     {
    801       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    802                            "fork");
    803       GNUNET_free (input);
    804       GNUNET_process_destroy (stage->proc);
    805       stage->proc = NULL;
    806       return false;
    807     }
    808     GNUNET_free (input);
    809     tc->active_stages++;
    810     stage->cwh = GNUNET_wait_child (stage->proc,
    811                                     &typst_done_cb,
    812                                     stage);
    813     GNUNET_assert (NULL != stage->cwh);
    814   }
    815   return true;
    816 }
    817 
    818 
    819 struct TALER_MHD_TypstContext *
    820 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 {
    830   static enum GNUNET_GenericReturnValue once = GNUNET_NO;
    831   struct TALER_MHD_TypstContext *tc;
    832 
    833   switch (once)
    834   {
    835   case GNUNET_OK:
    836     break;
    837   case GNUNET_NO:
    838     if (GNUNET_SYSERR ==
    839         GNUNET_OS_check_helper_binary ("typst",
    840                                        false,
    841                                        NULL))
    842     {
    843       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    844                   "`typst' command not found\n");
    845       once = GNUNET_SYSERR;
    846       return NULL;
    847     }
    848     if (GNUNET_SYSERR ==
    849         GNUNET_OS_check_helper_binary ("pdftk",
    850                                        false,
    851                                        NULL))
    852     {
    853       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    854                   "`pdftk' command not found\n");
    855       once = GNUNET_SYSERR;
    856       return NULL;
    857     }
    858     once = GNUNET_OK;
    859     break;
    860   case GNUNET_SYSERR:
    861     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    862                 "PDF generation initialization failed before, not even trying again\n");
    863     return NULL;
    864   }
    865   tc = GNUNET_new (struct TALER_MHD_TypstContext);
    866   tc->pd = pd;
    867   tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
    868   tc->remove_on_exit = remove_on_exit;
    869   tc->cb = cb;
    870   tc->cb_cls = cb_cls;
    871   if (NULL == mkdtemp (tc->tmpdir))
    872   {
    873     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    874                               "mkdtemp",
    875                               tc->tmpdir);
    876     GNUNET_free (tc->tmpdir);
    877     TALER_MHD_typst_cancel (tc);
    878     return NULL;
    879   }
    880   GNUNET_asprintf (&tc->output_file,
    881                    "%s/final.pdf",
    882                    tc->tmpdir);
    883 
    884   /* setup typst stages */
    885   {
    886     char *template_path;
    887 
    888     if (GNUNET_OK !=
    889         GNUNET_CONFIGURATION_get_value_string (cfg,
    890                                                cfg_section_name,
    891                                                "TYPST_TEMPLATES",
    892                                                &template_path))
    893     {
    894       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    895                                  cfg_section_name,
    896                                  "TYPST_TEMPLATES");
    897       TALER_MHD_typst_cancel (tc);
    898       return NULL;
    899     }
    900     if ('@' != template_path[0])
    901       template_path = GNUNET_CONFIGURATION_expand_dollar (cfg,
    902                                                           template_path);
    903     tc->stages = GNUNET_new_array (num_documents,
    904                                    struct TypstStage);
    905     tc->num_stages = num_documents;
    906     for (unsigned int i = 0; i<num_documents; i++)
    907     {
    908       tc->stages[i].tc = tc;
    909       if (! setup_stage (&tc->stages[i],
    910                          i,
    911                          tc->tmpdir,
    912                          template_path,
    913                          &docs[i]))
    914       {
    915         char err[128];
    916 
    917         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    918                     "Typst setup failed on stage %u\n",
    919                     i);
    920         GNUNET_snprintf (err,
    921                          sizeof (err),
    922                          "Typst setup failed on stage %u",
    923                          i);
    924         typst_context_fail_async (tc,
    925                                   TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
    926                                   err);
    927         GNUNET_free (template_path);
    928         return tc;
    929       }
    930     }
    931     GNUNET_free (template_path);
    932   }
    933   if (0 == tc->active_stages)
    934   {
    935     tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
    936                                       tc);
    937   }
    938   return tc;
    939 }
    940 
    941 
    942 struct MHD_Response *
    943 TALER_MHD_response_from_pdf_file (const char *filename)
    944 {
    945   struct MHD_Response *resp;
    946   struct stat s;
    947   int fd;
    948 
    949   fd = open (filename,
    950              O_RDONLY);
    951   if (-1 == fd)
    952   {
    953     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
    954                               "open",
    955                               filename);
    956     return NULL;
    957   }
    958   if (0 !=
    959       fstat (fd,
    960              &s))
    961   {
    962     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
    963                               "fstat",
    964                               filename);
    965     GNUNET_assert (0 == close (fd));
    966     return NULL;
    967   }
    968   resp = MHD_create_response_from_fd (s.st_size,
    969                                       fd);
    970   if (NULL == resp)
    971   {
    972     GNUNET_break (0);
    973     GNUNET_assert (0 == close (fd));
    974     return NULL;
    975   }
    976   TALER_MHD_add_global_headers (resp,
    977                                 false);
    978   GNUNET_break (MHD_YES ==
    979                 MHD_add_response_header (resp,
    980                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    981                                          "application/pdf"));
    982   return resp;
    983 }