commit 694cd42c3f4fdae4dc0a2261cc7da88e12e2a62f
parent c36cde50657a2ce89d79b832208587dac72e7fff
Author: Florian Dold <dold@taler.net>
Date: Mon, 31 Aug 2026 22:39:16 +0200
exchange: add AML form PDF rendering endpoint
Diffstat:
11 files changed, 717 insertions(+), 45 deletions(-)
diff --git a/src/exchange/meson.build b/src/exchange/meson.build
@@ -167,6 +167,7 @@ taler_exchange_httpd_SOURCES = [
'taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c',
'taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c',
'taler-exchange-httpd_post-aml-OFFICER_PUB-decision.c',
+ 'taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.c',
'taler-exchange-httpd_get-aml-OFFICER_PUB-decisions.c',
'taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.c',
'taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c',
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
@@ -36,6 +36,7 @@
#include \
"taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h"
#include "taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h"
+#include "taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.h"
#include "taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.h"
#include "taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h"
#include "taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.h"
@@ -434,6 +435,59 @@ typedef enum MHD_Result
/**
+ * Verify the generic AML officer authentication signature.
+ *
+ * @param rc request context
+ * @param officer_pub public key claimed by the request
+ * @param[out] mhd_ret response result on failure
+ * @return true if authentication succeeded
+ */
+static bool
+verify_aml_query_signature (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ enum MHD_Result *mhd_ret)
+{
+ const char *sig_hdr;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+
+ sig_hdr = MHD_lookup_connection_value (
+ rc->connection,
+ MHD_HEADER_KIND,
+ TALER_AML_OFFICER_SIGNATURE_HEADER);
+ if ( (NULL == sig_hdr) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (sig_hdr,
+ strlen (sig_hdr),
+ &officer_sig,
+ sizeof (officer_sig))) )
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ TALER_AML_OFFICER_SIGNATURE_HEADER);
+ return false;
+ }
+ if (GNUNET_OK !=
+ TALER_officer_aml_query_verify (officer_pub,
+ &officer_sig))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID,
+ sig_hdr);
+ return false;
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ return true;
+}
+
+
+/**
* Handle a "/aml/$OFFICER_PUB/$OP" POST request. Parses the "officer_pub"
* EdDSA key of the officer and demultiplexes based on $OP.
*
@@ -460,16 +514,33 @@ handle_post_aml (struct TEH_RequestContext *rc,
*/
AmlOpPostHandler handler;
+ /**
+ * Whether a read-only officer may use this operation.
+ */
+ bool allow_read_only;
+
+ /**
+ * Whether the generic AML query signature header is required.
+ */
+ bool query_auth;
+
} h[] = {
{
.op = "decision",
.handler = &TEH_handler_post_aml_decision
},
{
+ .op = "render-form",
+ .handler = &TEH_handler_post_aml_render_form,
+ .allow_read_only = true,
+ .query_auth = true
+ },
+ {
.op = NULL,
.handler = NULL
},
};
+ unsigned int hi;
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
@@ -484,6 +555,23 @@ handle_post_aml (struct TEH_RequestContext *rc,
args[0]);
}
+ for (hi = 0; NULL != h[hi].op; hi++)
+ if (0 == strcmp (h[hi].op,
+ args[1]))
+ break;
+ if (NULL == h[hi].op)
+ return r404 (rc->connection,
+ args[1]);
+ if (h[hi].query_auth)
+ {
+ enum MHD_Result ret;
+
+ if (! verify_aml_query_signature (rc,
+ &officer_pub,
+ &ret))
+ return ret;
+ }
+
{
enum GNUNET_DB_QueryStatus qs;
bool ro;
@@ -509,7 +597,8 @@ handle_post_aml (struct TEH_RequestContext *rc,
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
- if (ro)
+ if (ro &&
+ (! h[hi].allow_read_only))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
@@ -518,15 +607,9 @@ handle_post_aml (struct TEH_RequestContext *rc,
"read-only");
}
}
-
- for (unsigned int i = 0; NULL != h[i].op; i++)
- if (0 == strcmp (h[i].op,
- args[1]))
- return h[i].handler (rc,
- &officer_pub,
- root);
- return r404 (rc->connection,
- args[1]);
+ return h[hi].handler (rc,
+ &officer_pub,
+ root);
}
@@ -641,39 +724,12 @@ handle_get_aml (struct TEH_RequestContext *rc,
args[0]);
}
{
- const char *sig_hdr;
- struct TALER_AmlOfficerSignatureP officer_sig;
+ enum MHD_Result ret;
- sig_hdr = MHD_lookup_connection_value (
- rc->connection,
- MHD_HEADER_KIND,
- TALER_AML_OFFICER_SIGNATURE_HEADER);
- if ( (NULL == sig_hdr) ||
- (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (sig_hdr,
- strlen (sig_hdr),
- &officer_sig,
- sizeof (officer_sig))) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
- TALER_AML_OFFICER_SIGNATURE_HEADER);
- }
- if (GNUNET_OK !=
- TALER_officer_aml_query_verify (&officer_pub,
- &officer_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID,
- sig_hdr);
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (! verify_aml_query_signature (rc,
+ &officer_pub,
+ &ret))
+ return ret;
}
{
@@ -2788,6 +2844,7 @@ do_shutdown (void *cls)
TEH_kyc_start_cleanup ();
TEH_aml_decision_cleanup ();
TEH_handler_aml_attributes_get_cleanup ();
+ TEH_handler_post_aml_render_form_cleanup ();
TALER_KYCLOGIC_kyc_done ();
TALER_MHD_daemons_destroy ();
TEH_keys_finished ();
diff --git a/src/exchange/taler-exchange-httpd_get-config.h b/src/exchange/taler-exchange-httpd_get-config.h
@@ -41,7 +41,7 @@
*
* Returned via both /config and /keys endpoints.
*/
-#define EXCHANGE_PROTOCOL_VERSION "40:0:6"
+#define EXCHANGE_PROTOCOL_VERSION "41:0:7"
/**
diff --git a/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.c b/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.c
@@ -0,0 +1,227 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.c
+ * @brief Render submitted AML form data as PDF
+ */
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.h"
+
+
+/**
+ * State of an asynchronous form rendering request.
+ */
+struct RenderContext
+{
+ struct RenderContext *next;
+ struct RenderContext *prev;
+ struct TEH_RequestContext *rc;
+ struct TALER_MHD_TypstContext *tc;
+ struct MHD_Response *response;
+ unsigned int http_status;
+ json_t *data;
+ struct TALER_MHD_TypstDocument doc;
+};
+
+
+/**
+ * Requests currently suspended while Typst runs.
+ */
+static struct RenderContext *render_head;
+static struct RenderContext *render_tail;
+
+
+void
+TEH_handler_post_aml_render_form_cleanup (void)
+{
+ struct RenderContext *rctx;
+
+ while (NULL != (rctx = render_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (render_head,
+ render_tail,
+ rctx);
+ MHD_resume_connection (rctx->rc->connection);
+ }
+}
+
+
+/**
+ * Release a request context.
+ *
+ * @param[in] rc request being cleaned up
+ */
+static void
+free_render_context (struct TEH_RequestContext *rc)
+{
+ struct RenderContext *rctx = rc->rh_ctx;
+
+ if (NULL != rctx->tc)
+ {
+ TALER_MHD_typst_cancel (rctx->tc);
+ rctx->tc = NULL;
+ }
+ if (NULL != rctx->response)
+ {
+ MHD_destroy_response (rctx->response);
+ rctx->response = NULL;
+ }
+ json_decref (rctx->data);
+ GNUNET_free (rctx);
+}
+
+
+/**
+ * Receive the result of the Typst operation.
+ *
+ * @param cls a `struct RenderContext *`
+ * @param tr result from Typst
+ */
+static void
+render_finished (void *cls,
+ const struct TALER_MHD_TypstResponse *tr)
+{
+ struct RenderContext *rctx = cls;
+
+ rctx->tc = NULL;
+ GNUNET_CONTAINER_DLL_remove (render_head,
+ render_tail,
+ rctx);
+ MHD_resume_connection (rctx->rc->connection);
+ TALER_MHD_daemon_trigger ();
+ if (TALER_EC_NONE != tr->ec)
+ {
+ rctx->http_status = TALER_ErrorCode_get_http_status (tr->ec);
+ rctx->response = TALER_MHD_make_error (tr->ec,
+ tr->details.hint);
+ return;
+ }
+ rctx->response = TALER_MHD_response_from_pdf_file (tr->details.filename);
+ if (NULL == rctx->response)
+ {
+ rctx->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ rctx->response = TALER_MHD_make_error (
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "failed to open rendered PDF");
+ return;
+ }
+ rctx->http_status = MHD_HTTP_OK;
+}
+
+
+enum MHD_Result
+TEH_handler_post_aml_render_form (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root)
+{
+ struct RenderContext *rctx = rc->rh_ctx;
+ const char *form_id;
+ const json_t *form_version;
+
+ (void) officer_pub;
+ if (NULL != rctx)
+ {
+ if (NULL == rctx->response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ return MHD_queue_response (rc->connection,
+ rctx->http_status,
+ rctx->response);
+ }
+ if (! json_is_object (root))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "request body must be a JSON object");
+ }
+ form_id = json_string_value (json_object_get (root,
+ "FORM_ID"));
+ if (NULL == form_id)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "FORM_ID");
+ }
+ form_version = json_object_get (root,
+ "FORM_VERSION");
+ if ( (NULL != form_version) &&
+ ( (! json_is_integer (form_version)) ||
+ (0 > json_integer_value (form_version)) ) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "FORM_VERSION");
+ }
+
+ rctx = GNUNET_new (struct RenderContext);
+ rctx->rc = rc;
+ rctx->data = json_deep_copy (root);
+ if (NULL == rctx->data)
+ {
+ GNUNET_free (rctx);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "failed to copy form data");
+ }
+ if (NULL != TEH_global_pdf_form_data)
+ GNUNET_assert (0 ==
+ json_object_update_missing (rctx->data,
+ TEH_global_pdf_form_data));
+ rctx->doc.form_name = json_string_value (json_object_get (rctx->data,
+ "FORM_ID"));
+ rctx->doc.form_version = NULL;
+ rctx->doc.data = rctx->data;
+ rc->rh_ctx = rctx;
+ rc->rh_cleaner = &free_render_context;
+
+ rctx->tc = TALER_MHD_typst (TALER_EXCHANGE_project_data (),
+ TEH_cfg,
+ true,
+ "exchange",
+ 1,
+ &rctx->doc,
+ &render_finished,
+ rctx);
+ if (NULL == rctx->tc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client requested form PDF, but Typst is unavailable\n");
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
+ NULL);
+ }
+ GNUNET_CONTAINER_DLL_insert (render_head,
+ render_tail,
+ rctx);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+}
+
+
+/* end of taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.c */
diff --git a/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.h b/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file taler-exchange-httpd_post-aml-OFFICER_PUB-render-form.h
+ * @brief Handle POST /aml/$OFFICER_PUB/render-form requests
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_AML_OFFICER_PUB_RENDER_FORM_H
+#define TALER_EXCHANGE_HTTPD_POST_AML_OFFICER_PUB_RENDER_FORM_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Render submitted AML form data as a PDF.
+ *
+ * @param rc request context
+ * @param officer_pub authenticated AML officer
+ * @param root submitted form data
+ * @return MHD result code
+ */
+enum MHD_Result
+TEH_handler_post_aml_render_form (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root);
+
+
+/**
+ * Stop all in-flight form rendering requests.
+ */
+void
+TEH_handler_post_aml_render_form_cleanup (void);
+
+#endif
diff --git a/src/include/taler/exchange/meson.build b/src/include/taler/exchange/meson.build
@@ -21,6 +21,7 @@ talerexchange_headers = [
'get-reserves-RESERVE_PUB-history.h',
'get-transfers-WTID.h',
'post-aml-OFFICER_PUB-decision.h',
+ 'post-aml-OFFICER_PUB-render-form.h',
'post-auditors-AUDITOR_PUB-H_DENOM_PUB.h',
'post-batch-deposit.h',
'post-blinding-prepare.h',
diff --git a/src/include/taler/exchange/post-aml-OFFICER_PUB-render-form.h b/src/include/taler/exchange/post-aml-OFFICER_PUB-render-form.h
@@ -0,0 +1,117 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+*/
+/**
+ * @file include/taler/exchange/post-aml-OFFICER_PUB-render-form.h
+ * @brief C interface for POST /aml/$OFFICER_PUB/render-form
+ */
+#ifndef _TALER_EXCHANGE_POST_AML_OFFICER_PUB_RENDER_FORM_H
+#define _TALER_EXCHANGE_POST_AML_OFFICER_PUB_RENDER_FORM_H
+
+#include <taler/exchange/common.h>
+
+
+/**
+ * Handle for a POST /aml/$OFFICER_PUB/render-form operation.
+ */
+struct TALER_EXCHANGE_PostAmlRenderFormHandle;
+
+
+/**
+ * Response from POST /aml/$OFFICER_PUB/render-form.
+ */
+struct TALER_EXCHANGE_PostAmlRenderFormResponse
+{
+ /**
+ * HTTP response information.
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for an HTTP 200 response.
+ */
+ struct
+ {
+ /**
+ * PDF bytes. Borrowed and only valid during the callback.
+ */
+ const void *pdf;
+
+ /**
+ * Number of bytes in @e pdf.
+ */
+ size_t pdf_size;
+ } ok;
+ } details;
+};
+
+
+#ifndef TALER_EXCHANGE_POST_AML_RENDER_FORM_RESULT_CLOSURE
+#define TALER_EXCHANGE_POST_AML_RENDER_FORM_RESULT_CLOSURE void
+#endif
+
+
+/**
+ * Callback for POST /aml/$OFFICER_PUB/render-form.
+ *
+ * @param cls closure
+ * @param response exchange response
+ */
+typedef void
+(*TALER_EXCHANGE_PostAmlRenderFormCallback)(
+ TALER_EXCHANGE_POST_AML_RENDER_FORM_RESULT_CLOSURE *cls,
+ const struct TALER_EXCHANGE_PostAmlRenderFormResponse *response);
+
+
+/**
+ * Set up a form rendering operation. The operation must be started explicitly.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param officer_priv private key of the officer
+ * @param form completed form data; must be a JSON object containing FORM_ID
+ * @return operation handle, NULL on invalid arguments
+ */
+struct TALER_EXCHANGE_PostAmlRenderFormHandle *
+TALER_EXCHANGE_post_aml_render_form_create (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ const json_t *form);
+
+
+/**
+ * Start POST /aml/$OFFICER_PUB/render-form.
+ *
+ * @param[in,out] parfh operation handle
+ * @param cb result callback
+ * @param cb_cls closure for @a cb
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_EXCHANGE_post_aml_render_form_start (
+ struct TALER_EXCHANGE_PostAmlRenderFormHandle *parfh,
+ TALER_EXCHANGE_PostAmlRenderFormCallback cb,
+ TALER_EXCHANGE_POST_AML_RENDER_FORM_RESULT_CLOSURE *cb_cls);
+
+
+/**
+ * Cancel POST /aml/$OFFICER_PUB/render-form.
+ *
+ * @param[in] parfh operation handle
+ */
+void
+TALER_EXCHANGE_post_aml_render_form_cancel (
+ struct TALER_EXCHANGE_PostAmlRenderFormHandle *parfh);
+
+#endif
diff --git a/src/include/taler/taler_exchange_service.h b/src/include/taler/taler_exchange_service.h
@@ -65,6 +65,7 @@
#include <taler/exchange/get-aml-OFFICER_PUB-measures.h>
#include <taler/exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h>
#include <taler/exchange/post-aml-OFFICER_PUB-decision.h>
+#include <taler/exchange/post-aml-OFFICER_PUB-render-form.h>
#include <taler/exchange/post-management-partners.h>
#include <taler/exchange/post-management-auditors.h>
#include <taler/exchange/post-management-auditors-AUDITOR_PUB-disable.h>
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
@@ -35,7 +35,7 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define EXCHANGE_PROTOCOL_CURRENT 39
+#define EXCHANGE_PROTOCOL_CURRENT 41
/**
* How many versions are we backwards compatible with?
diff --git a/src/lib/exchange_api_post-aml-OFFICER_PUB-render-form.c b/src/lib/exchange_api_post-aml-OFFICER_PUB-render-form.c
@@ -0,0 +1,227 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 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.
+*/
+/**
+ * @file lib/exchange_api_post-aml-OFFICER_PUB-render-form.c
+ * @brief Implementation of POST /aml/$OFFICER_PUB/render-form
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include "taler/taler_curl_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_util.h"
+#include "taler/exchange/post-aml-OFFICER_PUB-render-form.h"
+#include "exchange_api_curl_defaults.h"
+
+
+struct TALER_EXCHANGE_PostAmlRenderFormHandle
+{
+ struct GNUNET_CURL_Context *ctx;
+ struct GNUNET_CURL_Job *job;
+ struct TALER_CURL_PostContext post_ctx;
+ char *base_url;
+ char *url;
+ struct TALER_AmlOfficerPrivateKeyP officer_priv;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ json_t *form;
+ TALER_EXCHANGE_PostAmlRenderFormCallback cb;
+ TALER_EXCHANGE_POST_AML_RENDER_FORM_RESULT_CLOSURE *cb_cls;
+};
+
+
+/**
+ * Complete a raw PDF request.
+ */
+static void
+handle_render_form_finished (void *cls,
+ long response_code,
+ const void *body,
+ size_t body_size)
+{
+ struct TALER_EXCHANGE_PostAmlRenderFormHandle *parfh = cls;
+ json_t *json = NULL;
+ struct TALER_EXCHANGE_PostAmlRenderFormResponse response = {
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ parfh->job = NULL;
+ if (MHD_HTTP_OK == response_code)
+ {
+ response.details.ok.pdf = body;
+ response.details.ok.pdf_size = body_size;
+ }
+ else
+ {
+ json_error_t error;
+
+ if ( (NULL != body) &&
+ (0 != body_size) )
+ json = json_loadb (body,
+ body_size,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ response.hr.reply = json;
+ if (0 == response_code)
+ {
+ response.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ response.hr.hint = "server offline?";
+ }
+ else
+ {
+ response.hr.ec = TALER_JSON_get_error_code (json);
+ response.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ }
+ if (NULL != parfh->cb)
+ {
+ parfh->cb (parfh->cb_cls,
+ &response);
+ parfh->cb = NULL;
+ }
+ if (NULL != json)
+ json_decref (json);
+ TALER_EXCHANGE_post_aml_render_form_cancel (parfh);
+}
+
+
+struct TALER_EXCHANGE_PostAmlRenderFormHandle *
+TALER_EXCHANGE_post_aml_render_form_create (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ const json_t *form)
+{
+ struct TALER_EXCHANGE_PostAmlRenderFormHandle *parfh;
+
+ if ( (NULL == ctx) ||
+ (NULL == url) ||
+ (NULL == officer_priv) ||
+ (! json_is_object (form)) ||
+ (! json_is_string (json_object_get (form,
+ "FORM_ID"))) )
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ parfh = GNUNET_new (struct TALER_EXCHANGE_PostAmlRenderFormHandle);
+ parfh->ctx = ctx;
+ parfh->base_url = GNUNET_strdup (url);
+ parfh->officer_priv = *officer_priv;
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &parfh->officer_pub.eddsa_pub);
+ parfh->form = json_deep_copy (form);
+ if (NULL == parfh->form)
+ {
+ TALER_EXCHANGE_post_aml_render_form_cancel (parfh);
+ return NULL;
+ }
+ return parfh;
+}
+
+
+enum TALER_ErrorCode
+TALER_EXCHANGE_post_aml_render_form_start (
+ struct TALER_EXCHANGE_PostAmlRenderFormHandle *parfh,
+ TALER_EXCHANGE_PostAmlRenderFormCallback cb,
+ TALER_EXCHANGE_POST_AML_RENDER_FORM_RESULT_CLOSURE *cb_cls)
+{
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char pub_str[sizeof (parfh->officer_pub) * 2];
+ char sig_str[sizeof (officer_sig) * 2];
+ char *path;
+ char *header;
+ char *end;
+ CURL *eh;
+ struct curl_slist *headers;
+
+ if (NULL != parfh->job)
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ parfh->cb = cb;
+ parfh->cb_cls = cb_cls;
+ TALER_officer_aml_query_sign (&parfh->officer_priv,
+ &officer_sig);
+ end = GNUNET_STRINGS_data_to_string (&parfh->officer_pub,
+ sizeof (parfh->officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_asprintf (&path,
+ "aml/%s/render-form",
+ pub_str);
+ parfh->url = TALER_url_join (parfh->base_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ if (NULL == parfh->url)
+ return TALER_EC_GENERIC_CONFIGURATION_INVALID;
+ eh = TALER_EXCHANGE_curl_easy_get_ (parfh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&parfh->post_ctx,
+ eh,
+ parfh->form)) )
+ {
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ end = GNUNET_STRINGS_data_to_string (&officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+ GNUNET_asprintf (&header,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ headers = curl_slist_append (parfh->post_ctx.headers,
+ header);
+ GNUNET_free (header);
+ if (NULL == headers)
+ {
+ curl_easy_cleanup (eh);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ parfh->post_ctx.headers = headers;
+ parfh->job = GNUNET_CURL_job_add_raw (parfh->ctx,
+ eh,
+ parfh->post_ctx.headers,
+ &handle_render_form_finished,
+ parfh);
+ if (NULL == parfh->job)
+ {
+ curl_easy_cleanup (eh);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ return TALER_EC_NONE;
+}
+
+
+void
+TALER_EXCHANGE_post_aml_render_form_cancel (
+ struct TALER_EXCHANGE_PostAmlRenderFormHandle *parfh)
+{
+ if (NULL == parfh)
+ return;
+ if (NULL != parfh->job)
+ {
+ GNUNET_CURL_job_cancel (parfh->job);
+ parfh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&parfh->post_ctx);
+ if (NULL != parfh->form)
+ json_decref (parfh->form);
+ GNUNET_free (parfh->url);
+ GNUNET_free (parfh->base_url);
+ GNUNET_free (parfh);
+}
+
+
+/* end of exchange_api_post-aml-OFFICER_PUB-render-form.c */
diff --git a/src/lib/meson.build b/src/lib/meson.build
@@ -27,6 +27,7 @@ libtalerexchange_la_SOURCES = [
'exchange_api_curl_defaults.c',
'exchange_api_handle.c',
'exchange_api_post-aml-OFFICER_PUB-decision.c',
+ 'exchange_api_post-aml-OFFICER_PUB-render-form.c',
'exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c',
'exchange_api_post-batch-deposit.c',
'exchange_api_post-blinding-prepare.c',
@@ -146,4 +147,3 @@ test(
suite: ['stefan', 'installcheck'],
)
-