commit f033d2c9f22d0efb1c8800cbf5d863416456b5ed
parent 8c17cedf9312c3d4ba6882336285b176eda51bef
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 16 Nov 2025 22:32:25 +0100
finish typst test
Diffstat:
8 files changed, 440 insertions(+), 393 deletions(-)
diff --git a/configure.ac b/configure.ac
@@ -522,6 +522,7 @@ AM_CONDITIONAL([HAVE_TWISTER], [false])
AC_CONFIG_FILES([Makefile
contrib/Makefile
+ contrib/typst/Makefile
doc/Makefile
doc/doxygen/Makefile
po/Makefile.in
diff --git a/contrib/Makefile.am.in b/contrib/Makefile.am.in
@@ -1,6 +1,6 @@
# This file is in the public domain.
-SUBDIRS = .
+SUBDIRS = . typst
tmplpkgdatadir = $(datadir)/taler-exchange/templates/
dist_tmplpkgdata_DATA = \
diff --git a/contrib/typst/Makefile.am b/contrib/typst/Makefile.am
@@ -0,0 +1,18 @@
+SUBDIRS = .
+
+formdatadir = $(datadir)/taler-exchange/typst-forms/
+dist_formdata_DATA = \
+ pointing_finger.svg \
+ VQF_902_1_customers.typ \
+ VQF_902_4.typ \
+ VQF_902_5.typ \
+ VQF_902_9.typ \
+ VQF_902_11.typ \
+ VQF_902_12.typ \
+ VQF_902_13.typ \
+ VQF_902_14.typ \
+ VQF_902_15.typ \
+ vss_vqf_verein.png
+
+EXTRA_DIST = \
+ $(dist_formdata_DATA)
diff --git a/src/include/taler/taler_mhd_lib.h b/src/include/taler/taler_mhd_lib.h
@@ -1167,21 +1167,74 @@ struct TALER_MHD_TypstDocument
/**
- * Create an HTTP response by generating PDFs using Typst and
- * combining them using pdftk.
+ * Context for generating PDF responses.
+ */
+struct TALER_MHD_TypstContext;
+
+
+/**
+ * Result from a #TALER_MHD_typst() operation.
+ */
+struct TALER_MHD_TypstResponse
+{
+
+ /**
+ * Error status of the operation.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Details depending on @e ec.
+ */
+ union
+ {
+ /**
+ * Hint if @e ec is not #TALER_EC_NONE.
+ */
+ const char *hint;
+
+ /**
+ * Filename with the result if @e ec is #TALER_EC_NONE.
+ */
+ const char *filename;
+ } details;
+
+};
+
+/**
+ * Function called with the result of a #TALER_MHD_typst() operation.
+ *
+ * @param cls closure
+ * @param tr result of the operation
+ */
+typedef void
+(*TALER_MHD_TypstResultCallback) (void *cls,
+ const struct TALER_MHD_TypstResponse *tr);
+
+
+/**
+ * Generate PDFs using Typst and combine them using pdftk. The
+ * file will be returned to @a cb and after @a cb returns all data
+ * will be deleted from the local disk.
*
* @param cfg configuration to use (where to find Typst templates)
+ * @param remove_on_exit should the directory be removed when done?
* @param cfg_section_name name of the configuration section to use
* @param num_documents length of the @a docs array
* @param docs list of documents to combine into one large PDF
- * @return NULL on error, otherwise HTTP response to return
+ * @param cb function to call with the resulting file(name)
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
*/
-struct MHD_Response *
+struct TALER_MHD_TypstContext *
TALER_MHD_typst (
const struct GNUNET_CONFIGURATION_Handle *cfg,
+ bool remove_on_exit,
const char *cfg_section_name,
unsigned int num_documents,
- const struct TALER_MHD_TypstDocument docs[static num_documents]);
+ const struct TALER_MHD_TypstDocument docs[static num_documents],
+ TALER_MHD_TypstResultCallback cb,
+ void *cb_cls);
/**
@@ -1189,7 +1242,17 @@ TALER_MHD_typst (
* To be used when the system is shutting down.
*/
void
-TALER_MHD_typst_stop_all (void);
+TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc);
+
+
+/**
+ * Create HTTP response from the PDF file at @a filename
+ *
+ * @param filename file to return as PDF
+ * @return NULL on error
+ */
+struct MHD_Response *
+TALER_MHD_response_from_pdf_file (const char *filename);
#endif
diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am
@@ -65,14 +65,13 @@ EXTRA_DIST = \
check_PROGRAMS = \
test_typst
-# TESTS = $(check_PROGRAMS)
+TESTS = $(check_PROGRAMS)
test_typst_SOURCES = \
test_typst.c
test_typst_LDADD = \
libtalermhd.la \
-ltalerutil \
- -lcurl \
-lmicrohttpd \
-lgnunetjson \
-ljansson \
diff --git a/src/mhd/mhd_typst.c b/src/mhd/mhd_typst.c
@@ -25,11 +25,6 @@
#include "taler/taler_mhd_lib.h"
#include <microhttpd.h>
-/**
- * Context for the entire typst build.
- */
-struct TypstContext;
-
/**
* Information about a specific typst invocation.
@@ -44,7 +39,7 @@ struct TypstStage
/**
* Typst context we are part of.
*/
- struct TypstContext *tc;
+ struct TALER_MHD_TypstContext *tc;
/**
* Handle to the typst process.
@@ -59,20 +54,10 @@ struct TypstStage
};
-struct TypstContext
+struct TALER_MHD_TypstContext
{
/**
- * Kept in a DLL.
- */
- struct TypstContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct TypstContext *prev;
-
- /**
* Directory where we create temporary files (or FIFOs) for the IPC.
*/
char *tmpdir;
@@ -93,9 +78,24 @@ struct TypstContext
struct GNUNET_ChildWaitHandle *cwh;
/**
- * Output pipe of pdftk.
+ * Callback to call on the final result.
+ */
+ TALER_MHD_TypstResultCallback cb;
+
+ /**
+ * Closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Task for async work.
+ */
+ struct GNUNET_SCHEDULER_Task *t;
+
+ /**
+ * Name of the final file created by pdftk.
*/
- struct GNUNET_DISK_PipeHandle *p;
+ char *output_file;
/**
* Length of the @e stages array.
@@ -107,34 +107,23 @@ struct TypstContext
*/
unsigned int active_stages;
+ /**
+ * Should the directory be removed when done?
+ */
+ bool remove_on_exit;
};
-/**
- * Active typst context DLL (for #TALER_MHD_typst_stop_all()).
- */
-static struct TypstContext *tc_head;
-
-/**
- * Active typst context DLL (for #TALER_MHD_typst_stop_all()).
- */
-static struct TypstContext *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 TypstContext *tc)
+void
+TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Cleaning up TypstContext\n");
- GNUNET_CONTAINER_DLL_remove (tc_head,
- tc_tail,
- tc);
+ if (NULL != tc->t)
+ {
+ GNUNET_SCHEDULER_cancel (tc->t);
+ tc->t = NULL;
+ }
for (unsigned int i = 0; i<tc->num_stages; i++)
{
struct TypstStage *stage = &tc->stages[i];
@@ -150,6 +139,7 @@ cleanup_tc (struct TypstContext *tc)
GNUNET_OS_process_kill (stage->proc,
SIGKILL));
GNUNET_OS_process_destroy (stage->proc);
+ stage->proc = NULL;
}
GNUNET_free (stage->filename);
}
@@ -166,28 +156,17 @@ cleanup_tc (struct TypstContext *tc)
SIGKILL));
GNUNET_OS_process_destroy (tc->proc);
}
+ GNUNET_free (tc->output_file);
if (NULL != tc->tmpdir)
{
- GNUNET_DISK_directory_remove (tc->tmpdir);
+ if (tc->remove_on_exit)
+ GNUNET_DISK_directory_remove (tc->tmpdir);
GNUNET_free (tc->tmpdir);
}
- if (NULL != tc->p)
- {
- GNUNET_DISK_pipe_close (tc->p);
- tc->p = NULL;
- }
GNUNET_free (tc);
}
-void
-TALER_MHD_typst_stop_all ()
-{
- while (NULL != tc_head)
- cleanup_tc (tc_head);
-}
-
-
/**
* Create file in @a tmpdir with one of the PDF inputs.
*
@@ -249,9 +228,35 @@ inline_pdf_stage (struct TypstStage *stage,
/**
+ * Generate a response for @a tc indicating an error of type @a ec.
+ *
+ * @param[in,out] tc context to fail
+ * @param ec error code to return
+ * @param hint hint text to return
+ */
+static void
+typst_context_fail (struct TALER_MHD_TypstContext *tc,
+ enum TALER_ErrorCode ec,
+ const char *hint)
+{
+ struct TALER_MHD_TypstResponse resp = {
+ .ec = ec,
+ .details.hint = hint
+ };
+
+ if (NULL != tc->cb)
+ {
+ tc->cb (tc->cb_cls,
+ &resp);
+ tc->cb = NULL;
+ }
+}
+
+
+/**
* Called when the pdftk helper exited.
*
- * @param cls our `struct TypstContext *`
+ * @param cls our `struct TALER_MHD_TypstContext *`
* @param type type of the process
* @param exit_code status code of the process
*/
@@ -260,9 +265,11 @@ pdftk_done_cb (void *cls,
enum GNUNET_OS_ProcessStatusType type,
long unsigned int exit_code)
{
- struct TypstContext *tc = cls;
+ struct TALER_MHD_TypstContext *tc = cls;
tc->cwh = NULL;
+ GNUNET_OS_process_destroy (tc->proc);
+ tc->proc = NULL;
switch (type)
{
case GNUNET_OS_PROCESS_UNKNOWN:
@@ -282,17 +289,33 @@ pdftk_done_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"pdftk exited with status %d\n",
(int) exit_code);
+ typst_context_fail (tc,
+ 42,
+ "pdftk failed");
+ }
+ else
+ {
+ struct TALER_MHD_TypstResponse resp = {
+ .ec = TALER_EC_NONE,
+ .details.filename = tc->output_file,
+ };
+
+ GNUNET_assert (NULL != tc->cb);
+ tc->cb (tc->cb_cls,
+ &resp);
+ tc->cb = NULL;
}
break;
case GNUNET_OS_PROCESS_SIGNALED:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"pdftk died with signal %d\n",
(int) exit_code);
+ typst_context_fail (tc,
+ 42,
+ "pdftk killed by signal");
break;
}
- GNUNET_OS_process_destroy (tc->proc);
- tc->proc = NULL;
- cleanup_tc (tc);
+ TALER_MHD_typst_cancel (tc);
}
@@ -300,24 +323,26 @@ pdftk_done_cb (void *cls,
* Function called once all of the individual stages are done.
* Triggers the pdftk run for @a tc.
*
- * @param[in,out] tc typst context to run pdftk for
+ * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for
*/
static void
-complete_response (struct TypstContext *tc)
+complete_response (void *cls)
{
+ struct TALER_MHD_TypstContext *tc = cls;
const char *argv[tc->num_stages + 5];
+ tc->t = NULL;
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 + 3] = tc->output_file;
argv[tc->num_stages + 4] = NULL;
tc->proc = GNUNET_OS_start_process_vap (
GNUNET_OS_INHERIT_STD_ERR,
NULL,
- tc->p,
+ NULL,
NULL,
"pdftk",
(char **) argv);
@@ -325,8 +350,7 @@ complete_response (struct TypstContext *tc)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"fork");
- GNUNET_DISK_pipe_close (tc->p);
- cleanup_tc (tc);
+ TALER_MHD_typst_cancel (tc);
return;
}
tc->cwh = GNUNET_wait_child (tc->proc,
@@ -349,7 +373,11 @@ typst_done_cb (void *cls,
long unsigned int exit_code)
{
struct TypstStage *stage = cls;
+ struct TALER_MHD_TypstContext *tc = stage->tc;
+ stage->cwh = NULL;
+ GNUNET_OS_process_destroy (stage->proc);
+ stage->proc = NULL;
switch (type)
{
case GNUNET_OS_PROCESS_UNKNOWN:
@@ -366,24 +394,52 @@ typst_done_cb (void *cls,
case GNUNET_OS_PROCESS_EXITED:
if (0 != exit_code)
{
+ char err[128];
+
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"typst exited with status %d\n",
(int) exit_code);
+ GNUNET_snprintf (err,
+ sizeof (err),
+ "Typst exited with status %d",
+ (int) exit_code);
+ typst_context_fail (tc,
+ 42,
+ err);
+ TALER_MHD_typst_cancel (tc);
+ return;
}
break;
case GNUNET_OS_PROCESS_SIGNALED:
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "typst died with signal %d\n",
- (int) exit_code);
+ {
+ char err[128];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "typst died with signal %d\n",
+ (int) exit_code);
+ GNUNET_snprintf (err,
+ sizeof (err),
+ "Typst died with signal %d",
+ (int) exit_code);
+ typst_context_fail (tc,
+ 42,
+ err);
+ TALER_MHD_typst_cancel (tc);
+ return;
+ }
break;
}
- stage->cwh = NULL;
- stage->tc->active_stages--;
- GNUNET_OS_process_destroy (stage->proc);
- stage->proc = NULL;
- if (0 != stage->tc->active_stages)
+ tc->active_stages--;
+ if (NULL != stage->proc)
+ {
+ GNUNET_OS_process_destroy (stage->proc);
+ stage->proc = NULL;
+ }
+ if (0 != tc->active_stages)
return;
- complete_response (stage->tc);
+ GNUNET_assert (NULL == tc->t);
+ tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
+ tc);
}
@@ -404,7 +460,6 @@ setup_stage (struct TypstStage *stage,
const char *template_path,
const struct TALER_MHD_TypstDocument *doc)
{
- char *template_fn;
char *input;
if (NULL == doc->form_name)
@@ -413,19 +468,6 @@ setup_stage (struct TypstStage *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 inputs */
{
@@ -438,41 +480,101 @@ setup_stage (struct TypstStage *stage,
if (GNUNET_OK !=
GNUNET_DISK_directory_create (dirname))
{
- GNUNET_free (template_fn);
GNUNET_free (dirname);
return false;
}
GNUNET_free (dirname);
}
- GNUNET_asprintf (&input,
- "%s/%u/input.typ",
- tmpdir,
- i);
+
+ /* Setup data input */
+ {
+ char *jfn;
+
+ GNUNET_asprintf (&jfn,
+ "%s/%u/input.json",
+ tmpdir,
+ i);
+ if (0 !=
+ json_dump_file (doc->data,
+ jfn,
+ JSON_INDENT (2)
+ // JSON_COMPACT
+ ))
+ {
+ GNUNET_break (0);
+ GNUNET_free (jfn);
+ return false;
+ }
+ GNUNET_free (jfn);
+ }
+
+ /* setup output file name */
GNUNET_asprintf (&stage->filename,
"%s/%u/input.pdf",
tmpdir,
i);
- if (0 !=
- symlink (template_fn, /* target */
- input))
+
+ /* setup main input Typst file */
{
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "symlink",
- template_fn);
- GNUNET_free (input);
+ char *intyp;
+ char *template_fn;
+
+ 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;
+ }
+ GNUNET_asprintf (&intyp,
+ "#import \"%s\": form\n"
+ "#form(json(\"%s/%u/input.json\"))\n",
+ template_fn,
+ tmpdir,
+ i);
+ GNUNET_asprintf (&input,
+ "%s/%u/input.typ",
+ tmpdir,
+ i);
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (input,
+ intyp,
+ strlen (intyp),
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ input);
+ GNUNET_free (input);
+ GNUNET_free (intyp);
+ GNUNET_free (template_fn);
+ return false;
+ }
GNUNET_free (template_fn);
- return false;
+ GNUNET_free (intyp);
}
- GNUNET_free (template_fn);
/* now setup typst invocation */
{
- const char *argv[4];
+ const char *argv[6];
argv[0] = "typst";
argv[1] = "compile";
- argv[2] = input;
- argv[3] = NULL;
+ /* This deliberately breaks the typst sandbox. Why? Because
+ they suck and do not support multiple roots, but we have
+ dynamic data in /tmp and resources outside of /tmp and
+ copying all the time is also bad. Typst should really
+ support multiple roots. */
+ argv[2] = "--root";
+ argv[3] = "/";
+ argv[4] = input;
+ argv[5] = NULL;
stage->proc = GNUNET_OS_start_process_vap (
GNUNET_OS_INHERIT_STD_ERR,
NULL,
@@ -498,15 +600,18 @@ setup_stage (struct TypstStage *stage,
}
-struct MHD_Response *
+struct TALER_MHD_TypstContext *
TALER_MHD_typst (
const struct GNUNET_CONFIGURATION_Handle *cfg,
+ bool remove_on_exit,
const char *cfg_section_name,
unsigned int num_documents,
- const struct TALER_MHD_TypstDocument docs[static num_documents])
+ const struct TALER_MHD_TypstDocument docs[static num_documents],
+ TALER_MHD_TypstResultCallback cb,
+ void *cb_cls)
{
static enum GNUNET_GenericReturnValue once = GNUNET_NO;
- struct TypstContext *tc;
+ struct TALER_MHD_TypstContext *tc;
switch (once)
{
@@ -538,21 +643,23 @@ TALER_MHD_typst (
case GNUNET_SYSERR:
return NULL;
}
- tc = GNUNET_new (struct TypstContext);
- GNUNET_CONTAINER_DLL_insert (tc_head,
- tc_tail,
- tc);
+ tc = GNUNET_new (struct TALER_MHD_TypstContext);
tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
+ tc->remove_on_exit = remove_on_exit;
+ tc->cb = cb;
+ tc->cb_cls = cb_cls;
if (NULL == mkdtemp (tc->tmpdir))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"mkdtemp",
tc->tmpdir);
GNUNET_free (tc->tmpdir);
- cleanup_tc (tc);
+ TALER_MHD_typst_cancel (tc);
return NULL;
}
- tc->p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ GNUNET_asprintf (&tc->output_file,
+ "%s/final.pdf",
+ tc->tmpdir);
/* setup typst stages */
{
@@ -567,7 +674,7 @@ TALER_MHD_typst (
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
cfg_section_name,
"TYPST_TEMPLATES");
- cleanup_tc (tc);
+ TALER_MHD_typst_cancel (tc);
return NULL;
}
tc->stages = GNUNET_new_array (num_documents,
@@ -582,7 +689,16 @@ TALER_MHD_typst (
template_path,
&docs[i]))
{
- cleanup_tc (tc);
+ char err[128];
+
+ GNUNET_snprintf (err,
+ sizeof (err),
+ "Typst setup failed on stage %u",
+ i);
+ typst_context_fail (tc,
+ 42,
+ err);
+ TALER_MHD_typst_cancel (tc);
return NULL;
}
}
@@ -590,28 +706,46 @@ TALER_MHD_typst (
}
if (0 == tc->active_stages)
{
- complete_response (tc);
+ tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
+ tc);
}
+ return tc;
+}
- /* return response */
+
+struct MHD_Response *
+TALER_MHD_response_from_pdf_file (const char *filename)
+{
+ struct MHD_Response *resp;
+ struct stat s;
+ int fd;
+
+ fd = open (filename,
+ O_RDONLY);
+ if (-1 == fd)
{
- struct MHD_Response *resp;
- struct GNUNET_DISK_FileHandle *fh;
- int fd;
-
- fh = GNUNET_DISK_pipe_detach_end (tc->p,
- GNUNET_DISK_PIPE_END_READ);
- fd = fh->fd;
- GNUNET_free (fh);
- resp = MHD_create_response_from_fd (MHD_SIZE_UNKNOWN,
- fd);
- TALER_MHD_add_global_headers (resp,
- false);
-
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "application/pdf"));
- return resp;
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return NULL;
+ }
+ if (0 !=
+ fstat (fd,
+ &s))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "fstat",
+ filename);
+ GNUNET_assert (0 == close (fd));
+ return NULL;
}
+ resp = MHD_create_response_from_fd (s.st_size,
+ fd);
+ TALER_MHD_add_global_headers (resp,
+ false);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/pdf"));
+ return resp;
}
diff --git a/src/mhd/test_typst.c b/src/mhd/test_typst.c
@@ -22,98 +22,47 @@
#include "taler/platform.h"
#include "taler/taler_util.h"
#include "taler/taler_mhd_lib.h"
-#include <curl/curl.h>
static int global_ret;
-static struct MHD_Daemon *mhd;
-
-static const struct GNUNET_CONFIGURATION_Handle *cfg;
-
-
-static MHD_RESULT
-handler_cb (void *cls,
- struct MHD_Connection *connection,
- const char *url,
- const char *method,
- const char *version,
- const char *upload_data,
- size_t *upload_data_size,
- void **con_cls)
-{
- struct MHD_Response *resp;
- json_t *forms[] = {
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("test",
- "value")),
- NULL,
- };
- struct TALER_MHD_TypstDocument docs[] = {
- {
- .form_name = "test_typst_1.typ",
- .data = forms[0],
- }
- };
-
- resp = TALER_MHD_typst (cfg,
- "test-typst",
- 1,
- docs);
-
- for (unsigned int i = 0; NULL != forms[i]; i++)
- json_decref (forms[i]);
- return MHD_queue_response (connection,
- MHD_HTTP_OK,
- resp);
-}
+static struct TALER_MHD_TypstContext *tc;
+static int keep_output;
static void
do_shutdown (void *cls)
{
- if (NULL != mhd)
+ if (NULL != tc)
{
- MHD_stop_daemon (mhd);
- mhd = NULL;
+ TALER_MHD_typst_cancel (tc);
+ tc = NULL;
}
}
+/**
+ * Function called with the result of a #TALER_MHD_typst() operation.
+ *
+ * @param cls closure
+ * @param tr result of the operation
+ */
static void
-run_client ()
+result_cb (void *cls,
+ const struct TALER_MHD_TypstResponse *tr)
{
- struct CURL *eh;
- FILE *f;
-
- f = fopen ("test_typst.pdf",
- "w+");
- if (NULL == f)
+ tc = NULL;
+ if (TALER_EC_NONE != tr->ec)
{
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "fopen",
- "test_typst.pdf");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PDF generation failed\n");
global_ret = 1;
- GNUNET_SCHEDULER_shutdown ();
- return;
}
- eh = curl_easy_init ();
- GNUNET_assert (NULL != eh);
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_URL,
- "http://localhost:9967/"));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_WRITEDATA,
- f));
- if (CURLE_OK !=
- curl_easy_perform (eh))
+ else
{
- fprintf (stderr,
- "Curl download failed!\n");
- global_ret = 1;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "PDF created at %s\n",
+ tr->details.filename);
}
- curl_easy_cleanup (eh);
GNUNET_SCHEDULER_shutdown ();
}
@@ -125,23 +74,51 @@ static void
run (void *cls,
char *const *args,
const char *cfgfile,
- const struct GNUNET_CONFIGURATION_Handle *c)
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
{
- cfg = c;
- mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_DEBUG,
- 9967,
- NULL, NULL,
- &handler_cb, NULL,
- NULL);
- if (NULL == mhd)
- {
- GNUNET_break (0);
- global_ret = 1;
- return;
- }
+ char *datadir
+ = GNUNET_OS_installation_get_path (TALER_EXCHANGE_project_data (),
+ GNUNET_OS_IPK_DATADIR);
+ json_t *forms[] = {
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("VQF_MEMBER_NUMBER",
+ "12345"),
+ GNUNET_JSON_pack_string ("FILE_NUMBER",
+ "-1"),
+ GNUNET_JSON_pack_string ("DATADIR",
+ datadir)),
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("VQF_MEMBER_NUMBER",
+ "54321"),
+ GNUNET_JSON_pack_string ("FILE_NUMBER",
+ "-1"),
+ GNUNET_JSON_pack_string ("DATADIR",
+ datadir)),
+ NULL,
+ };
+ struct TALER_MHD_TypstDocument docs[] = {
+ {
+ .form_name = "test_typst_1.typ",
+ .data = forms[0],
+ },
+ {
+ .form_name = "test_typst_1.typ",
+ .data = forms[1],
+ },
+ };
+
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
- run_client ();
+ tc = TALER_MHD_typst (cfg,
+ keep_output ? true : false,
+ "test-typst",
+ 2,
+ docs,
+ &result_cb,
+ NULL);
+ for (unsigned int i = 0; NULL != forms[i]; i++)
+ json_decref (forms[i]);
+ GNUNET_free (datadir);
}
@@ -149,24 +126,28 @@ int
main (int argc,
const char *const argv[])
{
- char *const argvx[] = {
- (char*) "test_typst",
- (char*) "-c", (char*) "test_typst.conf",
- (char*) "-L", (char*) "WARNING",
+ const char *argvx[] = {
+ "test_typst",
+ "-c", "test_typst.conf",
+ "-L", "INFO",
NULL
};
struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('k',
+ "keep-output",
+ "do not delete directory at the end of the test",
+ &keep_output),
GNUNET_GETOPT_OPTION_END
};
(void) argc;
(void) argv;
GNUNET_log_setup ("test-typst",
- "WARNING",
+ "INFO",
NULL);
if (GNUNET_OK !=
GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
- 5, argvx,
+ 5, (char **) argvx,
"test_typst",
"Test typst",
options,
diff --git a/src/mhd/test_typst_1.typ b/src/mhd/test_typst_1.typ
@@ -1,4 +1,4 @@
-
+// This file is in the public domain.
#let form(data) = {
set page(
paper: "a4",
@@ -8,8 +8,8 @@
columns: (1fr, 1fr),
align: (left, right),
text(size: 8pt)[
- VQF doc. Nr. 902.9#linebreak()
- Version of 1 December 2015
+ VQF doc. Nr. 90X.X#linebreak()
+ Version of 1 December 2100
],
text(size: 8pt)[
Page #here().page() of #counter(page).final().first()
@@ -26,28 +26,17 @@
data.at(key, default: default)
}
- // Helper function for checkbox
- let checkbox(checked) = {
- box(
- width: 3mm,
- height: 3mm,
- stroke: 0.5pt + black,
- inset: 0.3mm,
- if checked == true or checked == "true" {
- place(center + horizon, text(size: 8pt, sym.checkmark))
- }
- )
- }
-
// Header
align(center, text(size: 11pt, weight: "bold")[CONFIDENTIAL])
v(0.5em)
+ let datadir = get("DATADIR")
+
grid(
columns: (50%, 50%),
gutter: 1em,
- image("vss_vqf_verein.png", width: 80%),
+ image(datadir + "/typst-forms/vss_vqf_verein.png", width: 80%),
align(right)[
#table(
columns: (1fr, 1fr),
@@ -60,141 +49,4 @@
]
)
- v(1em)
-
- align(left, text(size: 14pt, weight: "bold")[Declaration of identity of the beneficial owner (A)])
-
- v(-1em)
- line(length:100%)
-
- v(1em)
-
- // Section 1: Contracting Partner
- text(size: 11pt, weight: "bold")[Contracting partner:]
-
- v(0.5em)
-
- table(
- columns: (1fr),
- stroke: 0.5pt + black,
- inset: 5pt,
- [#get("IDENTITY_CONTRACTING_PARTNER")]
- )
-
- v(1em)
-
- text()[The contracting partner hereby declares that the person(s) listed below is/are the beneficial owner(s) of the assets involved in the business relationship. If the contracting partner is also the sole beneficial owner of the assets, the contracting partner's detail must be set out below]
-
- v(1em)
-
- // Section 2: Beneficial Owners
- let owners = get("IDENTITY_LIST", default: ())
- let has_owners = type(owners) == array and owners.len() > 0
-
- if has_owners {
- for owner in owners {
- let get_owner(key) = {
- owner.at(key, default: "")
- }
-
- block(breakable: false)[
- #v(0.5em)
- #table(
- columns: (35%, 65%),
- stroke: 0.5pt + black,
- inset: 5pt,
- [Fullname:], [#get_owner("FULL_NAME")],
- [Date of birth:], [#get_owner("DATE_OF_BIRTH")],
- [Nationality:], [#get_owner("NATIONALITY")],
- [Actual address of domicile:], [#get_owner("DOMICILE_ADDRESS")]
- )
- #v(0.5em)
- ]
- }
- } else {
- block(breakable: false)[
- #v(0.5em)
- #table(
- columns: (35%, 65%),
- stroke: 0.5pt + black,
- inset: 5pt,
- [Surname(s):], [],
- [First name(s):], [],
- [Date(s) of birth:], [],
- [Nationality:], [],
- [Actual address of domicile:], []
- )
- #v(0.5em)
- ]
- }
-
- v(1em)
-
- text()[The contracting partner hereby undertakes to inform automatically of any changes to the information contained herein.]
-
- v(1.5em)
-
- // Signature Section
- let submitted_by = get("SUBMITTED_BY")
-
- if submitted_by == "CUSTOMER" {
- table(
- columns: (40%, 10%, 50%),
- stroke: 0.5pt + black,
- inset: 5pt,
- [Date:],
- [],
- [Signature(s):],
- [#get("SIGN_DATE")],
- [],
- [#get("SIGNATURE")]
- )
-
- v(1em)
-
- text(size: 9pt, style: "italic")[
- It is a criminal offence to deliberately provide false information on this form (article 251 of the Swiss Criminal Code, document forgery)
- ]
- } else if submitted_by == "AML_OFFICER" {
- text(weight: "bold")[Signed declaration by the customer]
-
- v(0.5em)
-
- text(size: 9pt)[The attachment contains the customer's signature on the beneficial owner declaration.]
-
- v(0.5em)
-
- table(
- columns: (1fr),
- stroke: 0.5pt + black,
- inset: 5pt,
- [Signed Document:],
- [#if (get("ATTACHMENT_SIGNED_DOCUMENT") != ""){
- [Document attached]
- } else {
- [No document]
- }
- ]
- )
- } else {
- text(weight: "bold")[Invalid submitter (#submitted_by)]
- }
}
-
-// Example usage:
-#form((
- "VQF_MEMBER_NUMBER": "12345",
- "FILE_NUMBER": "42",
- "IDENTITY_CONTRACTING_PARTNER": "Example Company AG\nBahnhofstrasse 1\n8001 Zurich\nSwitzerland",
- "IDENTITY_LIST": (
- (
- "FULL_NAME": "John Doe",
- "DATE_OF_BIRTH": "01.01.1980",
- "NATIONALITY": "CH",
- "DOMICILE_ADDRESS": "Teststrasse 123\n8001 Zurich"
- ),
- ),
- "SUBMITTED_BY": "CUSTOMER",
- "SIGNATURE": "John Doe",
- "SIGN_DATE": "10.11.2025",
-))
-\ No newline at end of file