commit 17e2243af51b84b8f074e082f0a03bfe1183990a
parent 09c8beb73e3de044f17d512b8ad75ed091bcc0e6
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Fri, 7 Nov 2025 17:40:21 +0100
draft logic for typist integration
Diffstat:
| A | src/mhd/mhd_typist.c | | | 581 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 581 insertions(+), 0 deletions(-)
diff --git a/src/mhd/mhd_typist.c b/src/mhd/mhd_typist.c
@@ -0,0 +1,581 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file mhd_typist.c
+ * @brief MHD utility functions for PDF generation
+ * @author Christian Grothoff
+ *
+ *
+ */
+#include "taler/platform.h"
+#include "taler/taler_util.h"
+#include "taler/taler_mhd_lib.h"
+
+
+/**
+ * Information about a document #TALER_MHD_typist() should output.
+ */
+struct TALER_MHD_TypistDocument
+{
+ /**
+ * Form name, used to determine the Typist template to use.
+ * NULL if @e data is a JSON string with a PDF to inline.
+ */
+ const char *form_name;
+
+ /**
+ * Form data.
+ */
+ const json_t *data;
+};
+
+
+/**
+ * Information about a specific typist invocation.
+ */
+struct TypistStage
+{
+ /**
+ * Name of the FIFO for the typist output.
+ */
+ char *filename;
+
+ /**
+ * Handle to the typist process.
+ */
+ struct GNUNET_OS_Process *proc;
+
+ /**
+ * Handle to be notified about stage completion.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+};
+
+
+struct TypistContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TypistContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TypistContext *prev;
+
+ /**
+ * Directory where we create temporary files (or FIFOs) for the IPC.
+ */
+ char *tmpdir;
+
+ /**
+ * Array of stages producing PDFs to be combined.
+ */
+ struct TypistStage *stages;
+
+ /**
+ * Handle for pdftk combining the various PDFs.
+ */
+ struct GNUNET_OS_Process *proc;
+
+ /**
+ * Handle to wait for @e proc to complete.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Length of the @e stages array.
+ */
+ unsigned int num_stages;
+
+};
+
+
+/**
+ * Active typist context DLL (for #TALER_MHD_typist_stop_all()).
+ */
+static struct TypistContext *tc_head;
+
+/**
+ * Active typist context DLL (for #TALER_MHD_typist_stop_all()).
+ */
+static struct TypistContext *tc_tail;
+
+
+/**
+ * Clean up @a cls, releasing all resources associated with it.
+ * Stops helper processes if they are still running.
+ *
+ * @param[in] tc context to clean up
+ */
+static void
+cleanup_tc (struct TypistContext *tc)
+{
+ GNUNET_CONTAINER_DLL_remove (tc_head,
+ tc_tail,
+ tc);
+ for (unsigned int i = 0; i<tc->num_stages; i++)
+ {
+ struct TypistStage *stage = tc->stages[i];
+
+ if (NULL != stage->cwh)
+ {
+ GNUNET_wait_child_cancel (stage->cwh);
+ stage->cwh = NULL;
+ }
+ if (NULL != stage->proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (stage->proc,
+ SIGKILL));
+ GNUNET_OS_process_destroy (stage->proc);
+ }
+ GNUNET_free (stage->filename);
+ }
+ GNUNET_free (tc->stages);
+ if (NULL != tc->cwh)
+ {
+ GNUNET_wait_child_cancel (tc->cwh);
+ tc->cwh = NULL;
+ }
+ if (NULL != tc->proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (tc->proc,
+ SIGKILL));
+ GNUNET_OS_process_destroy (tc->proc);
+ }
+ if (NULL != tc->tmpdir)
+ {
+ GNUNET_DISK_directory_remove (tc->tmpdir);
+ GNUNET_free (tc->tmpdir);
+ }
+ GNUNET_free (tc);
+}
+
+
+void
+TALER_MHD_typist_stop_all ()
+{
+ while (NULL != tc_head)
+ cleanup_tc (tc_head);
+}
+
+
+/**
+ * Create file in @a tmpdir with one of the PDF inputs.
+ *
+ * @param[out] stage initialized stage data
+ * @param tmpdir where to place temporary files
+ * @param data input JSON with PDF data
+ * @return true on success
+ */
+static bool
+inline_pdf_stage (struct TypistStage *stage,
+ const char *tmpdir,
+ const json_t *data)
+{
+ const char *str = json_string_value (data);
+ char *fn;
+ size_t n;
+ void *b;
+
+ if (NULL == str)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data_alloc (str,
+ strlen (str),
+ &b,
+ &n))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ GNUNET_asprintf (&fn,
+ "%s/external-XXXXXX",
+ tmpdir);
+ stage->filename = GNUNET_DISK_mktemp (fn);
+ if (NULL == stage->filename)
+ {
+ GNUNET_break (0);
+ GNUNET_free (b);
+ GNUNET_free (fn);
+ return false;
+ }
+ GNUNET_free (fn);
+
+ if (n !=
+ GNUNET_DISK_fn_write (stage->filename,
+ b,
+ n,
+ GNUNET_DISK_PERM_USER_READ
+ | GNUNET_DISK_PERM_USER_WRITE))
+ {
+ GNUNET_free (b);
+ GNUNET_free (stage->filename);
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Called when a typist helper exited.
+ *
+ * @param cls our `struct TypistStage *`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+typist_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct TypistStage *stage = cls;
+
+ stage->cwh = NULL;
+ switch (type)
+ {
+ case GNUNET_OS_PROCESS_UNKNOWN:
+ GNUNET_assert (0);
+ return;
+ case GNUNET_OS_PROCESS_RUNNING:
+ /* we should not get this notification */
+ GNUNET_break (0);
+ return;
+ case GNUNET_OS_PROCESS_STOPPED:
+ /* Someone is SIGSTOPing our helper!? */
+ GNUNET_break (0);
+ return;
+ case GNUNET_OS_PROCESS_EXITED:
+ if (0 != exit_code)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "typist exited with status %d\n",
+ (int) exit_code);
+ }
+ break;
+ case GNUNET_OS_PROCESS_SIGNALED:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "typist died with signal %d\n",
+ (int) exit_code);
+ break;
+ }
+ GNUNET_OS_process_destroy (stage->proc);
+ stage->proc = NULL;
+}
+
+
+/**
+ * Setup typist stage to produce one of the PDF inputs.
+ *
+ * @param[out] stage initialized stage data
+ * @param i index of the stage
+ * @param tmpdir where to place temporary files
+ * @param template_path where to find templates
+ * @param doc input document specification
+ * @return true on success
+ */
+static bool
+setup_stage (struct TypistStage *stage,
+ unsigned int i,
+ const char *tmpdir,
+ const char *template_path,
+ const struct TALER_MHD_TypistDocument *doc)
+{
+ char *template_fn;
+
+ if (NULL == doc->form_name)
+ {
+ return inline_pdf_stage (stage,
+ tmpdir,
+ doc->data);
+ }
+ GNUNET_asprintf (&template_fn,
+ "%s%s",
+ template_path,
+ doc->form_name);
+ if (GNUNET_YES !=
+ GNUNET_DISK_file_test_read (template_fn))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "access",
+ template_fn);
+ GNUNET_free (template_fn);
+ return false;
+ }
+
+ /* Setup fifo */
+ GNUNET_asprintf (&stage->filename,
+ "%s/%u",
+ tmpdir,
+ i);
+ if (0 !=
+ mkfifo (stage->filename,
+ S_IRUSR | S_IWUSR))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "mkfifo",
+ stage->filename);
+ GNUNET_free (stage->filename);
+ GNUNET_free (template_fn);
+ return false;
+ }
+
+
+ /* now setup typist stage */
+ {
+ struct GNUNET_DISK_FileHandle *fh;
+ char *argv[42];
+
+ fh = GNUNET_DISK_file_open (stage->filename,
+ GNUNET_DISK_OPEN_WRITE,
+ GNUNET_DISK_PERM_NONE);
+ if (NULL == fh)
+ {
+ GNUNET_free (stage->filename);
+ GNUNET_free (template_fn);
+ return false;
+ }
+ // FIXME: check correct invocation!
+ argv[0] = "typist";
+ argv[1] = template_fn;
+ argv[2] = "-";
+ argv[3] = NULL;
+ tc->proc = GNUNET_OS_start_process_vap (
+ GNUNET_OS_INHERIT_STD_ERR,
+ NULL,
+ fh,
+ NULL,
+ "typist",
+ argv);
+ if (NULL == tc->proc)
+ {
+ GNUNET_log_stderror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ GNUNET_DISK_pipe_close (p);
+ cleanup_tc (tc);
+ return NULL;
+ }
+ stage->cwh = GNUNET_wait_child (tc->proc,
+ &typist_done_cb,
+ stage);
+ GNUNET_assert (NULL != stage->cwh);
+ }
+ return true;
+}
+
+
+/**
+ * Called when the pdftk helper exited.
+ *
+ * @param cls our `struct TypistContext *`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+pdftk_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct TypistContext *tc = cls;
+
+ tc->cwh = NULL;
+ switch (type)
+ {
+ case GNUNET_OS_PROCESS_UNKNOWN:
+ GNUNET_assert (0);
+ return;
+ case GNUNET_OS_PROCESS_RUNNING:
+ /* we should not get this notification */
+ GNUNET_break (0);
+ return;
+ case GNUNET_OS_PROCESS_STOPPED:
+ /* Someone is SIGSTOPing our helper!? */
+ GNUNET_break (0);
+ return;
+ case GNUNET_OS_PROCESS_EXITED:
+ if (0 != exit_code)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "pdftk exited with status %d\n",
+ (int) exit_code);
+ }
+ break;
+ case GNUNET_OS_PROCESS_SIGNALED:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "pdftk died with signal %d\n",
+ (int) exit_code);
+ break;
+ }
+ GNUNET_OS_process_destroy (tc->proc);
+ tc->proc = NULL;
+ cleanup_tc (tc);
+}
+
+
+struct MHD_Response *
+TALER_MHD_typist (
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *cfg_section_name,
+ unsigned int num_documents,
+ const struct TALER_MHD_TypistDocument docs[static num_documents])
+{
+ static enum GNUNET_GenericReturnValue once = GNUNET_NO;
+ struct TypistContext *tc;
+
+ switch (once)
+ {
+ case GNUNET_OK:
+ break;
+ case GNUNET_NO:
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_check_helper_binary ("typist",
+ false,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "`typist' command not found\n");
+ once = GNUNET_SYSERR;
+ return NULL;
+ }
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_check_helper_binary ("pdftk",
+ false,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "`pdftk' command not found\n");
+ once = GNUNET_SYSERR;
+ return NULL;
+ }
+ once = GNUNET_OK;
+ break;
+ case GNUNET_SYSERR:
+ return NULL;
+ }
+ tc = GNUNET_new (struct TypistContext);
+ GNUNET_CONTAINER_DLL_insert (tc_head,
+ tc_tail,
+ tc);
+ tc->tmpdir = GNUNET_strdup ("/tmp/taler-typist-XXXXXX");
+ if (NULL == mkdtemp (tc->tmpdir))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "mkdtemp",
+ tc->tmpdir);
+ GNUNET_free (tc->tmpdir);
+ cleanup_tc (tc);
+ return NULL;
+ }
+
+ /* setup typist stages */
+ {
+ char *template_path;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ cfg_section_name,
+ "TYPIST_TEMPLATES",
+ &template_path))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ cfg_section_name,
+ "TYPIST_TEMPLATES");
+ cleanup_tc (tc);
+ return NULL;
+ }
+ tc->stages = GNUNET_new_array (num_documents,
+ struct TypistStage);
+ tc->num_stages = num_documents;
+ for (unsigned int i = 0; i<num_documents; i++)
+ {
+ if (! setup_stage (&tc->stages[i],
+ i,
+ tc->tmpdir,
+ template_path,
+ &docs[i]))
+ {
+ cleanup_tc (tc);
+ return NULL;
+ }
+ }
+ GNUNET_free (template_path);
+ }
+
+ /* now setup pdftk stage */
+ {
+ struct GNUNET_DISK_PipeHandle *p;
+ char *argv[tc->num_stages + 5];
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+
+ argv[0] = "pdftk";
+ for (unsigned int i = 0; i<tc->num_stages; i++)
+ argv[i + 1] = tc->stages[i].filename;
+ argv[tc->num_stages + 1] = "cat";
+ argv[tc->num_stages + 2] = "output";
+ argv[tc->num_stages + 3] = "-";
+ argv[tc->num_stages + 4] = NULL;
+ tc->proc = GNUNET_OS_start_process_vap (
+ GNUNET_OS_INHERIT_STD_ERR,
+ NULL,
+ GNUNET_DISK_pipe_handle (p,
+ GNUNET_DISK_PF_END_WRITE),
+ NULL,
+ "pdftk",
+ argv);
+ if (NULL == tc->proc)
+ {
+ GNUNET_log_stderror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ GNUNET_DISK_pipe_close (p);
+ cleanup_tc (tc);
+ return NULL;
+ }
+ tc->cwh = GNUNET_wait_child (tc->proc,
+ &pdftk_done_cb,
+ tc);
+ GNUNET_assert (NULL != tc->cwh);
+
+ {
+ struct MHD_Response *resp;
+ struct GNUNET_DISK_FileHandle *fh;
+ int fd;
+
+ fh = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PF_END_READ);
+ fd = fh->fd;
+ GNUNET_free (fh);
+ resp = MHD_response_from_fd (MHD_SIZE_UNKNOWN,
+ fd);
+ TALER_MHD_add_global_headers (resp,
+ false);
+
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/pdf"));
+ return resp;
+ }
+ }
+}