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 (26424B)


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