aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/Makefile.am5
-rw-r--r--contrib/depleted_tip.en.must51
-rw-r--r--contrib/offer_tip.en.must144
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.c39
-rw-r--r--src/backend/taler-merchant-httpd_get-tips-ID.c145
-rw-r--r--src/backend/taler-merchant-httpd_mhd.c49
-rw-r--r--src/backend/taler-merchant-httpd_mhd.h9
-rw-r--r--src/backend/taler-merchant-httpd_templating.c483
-rw-r--r--src/backend/taler-merchant-httpd_templating.h70
9 files changed, 945 insertions, 50 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index 8edb184b..c362ddd5 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -1,5 +1,8 @@
pkgdatadir = $(prefix)/share/taler/merchant/templates/
dist_pkgdata_DATA = \
+ depleted_tip.en.must \
+ offer_refund.en.must \
+ offer_tip.en.must \
request_payment.en.must \
- offer_refund.en.must
+ show_order_details.en.must
diff --git a/contrib/depleted_tip.en.must b/contrib/depleted_tip.en.must
new file mode 100644
index 00000000..370e9b90
--- /dev/null
+++ b/contrib/depleted_tip.en.must
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!--
+ This file is part of GNU TALER.
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+-->
+
+<html data-taler-nojs="true">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <noscript>
+ <meta http-equiv="refresh" content="1">
+ </noscript>
+ <title>Status of your tip</title>
+ <!-- FIXME: inline this? How to best serve this without using 3rd party? -->
+ <link rel="stylesheet"
+ href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
+ crossorigin="anonymous">
+ <style>
+.content {
+ overflow-x: auto;
+ padding-left: 15%;
+ padding-right: 15%;
+}
+#main a:link, #main a:visited, #main a:hover, #main a:active {
+ color: black;
+}
+ </style>
+</head>
+
+<body>
+<h1>Tip already picked up</h1>
+
+<div>
+You have already picked up your tip.
+</div>
+
+</body>
+</html>
diff --git a/contrib/offer_tip.en.must b/contrib/offer_tip.en.must
new file mode 100644
index 00000000..339f64ff
--- /dev/null
+++ b/contrib/offer_tip.en.must
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<!--
+ This file is part of GNU TALER.
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+-->
+
+<html data-taler-nojs="true">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <noscript>
+ <meta http-equiv="refresh" content="1">
+ </noscript>
+ <title>Tip available</title>
+ <!-- FIXME-6460: allow taler-merchant-httpd to serve this, so we do not use 3rd party? -->
+ <link rel="stylesheet"
+ href="https://unpkg.com/purecss@2.0.3/build/pure-min.css"
+ integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ"
+ crossorigin="anonymous">
+ <style>
+.taler::before {
+ content: "❬";
+ color: #aa3939;
+}
+.taler::after {
+ content: "❭";
+ color: #aa3939;
+}
+.talerbar {
+ text-align: center;
+}
+.tt {
+ font-family: 'Lucida Console', Monaco, monospace;
+}
+.content {
+ overflow-x: auto;
+ padding-left: 15%;
+ padding-right: 15%;
+}
+.qr {
+ margin: auto;
+ text-align: center;
+}
+.qrtext {
+ width: max-content;
+ margin: auto;
+ transition: font-size 0.2s;
+ font-family: 'Lucida Console', Monaco, monospace;
+ font-size: 0.5em;
+}
+.qrtext:hover {
+ font-size: 1em;
+}
+.talerbar {
+ margin: 0;
+ bottom: 0;
+ background-color: #033;
+ color: white;
+ width: 100%;
+ padding: 1em;
+ overflow: auto;
+}
+
+body {
+ overflow-y: scroll;
+}
+@media (min-width: 500px) {
+ .content {
+ padding-bottom: 2em;
+ margin-right: 1em;
+ overflow-y: auto;
+ }
+}
+#main a:link, #main a:visited, #main a:hover, #main a:active {
+ color: black;
+}
+ </style>
+</head>
+
+<body>
+<script>
+ let checkUrl = FIXME-#6457_dold_tip_uri_to_URL("{{taler_tip_uri}}");
+ let delayMs = 500;
+ function check() {
+ let req = new XMLHttpRequest();
+ req.onreadystatechange = function () {
+ if (req.readyState === XMLHttpRequest.DONE) {
+ if (req.status === 410) {
+ document.location.reload(true);
+ }
+ setTimeout(check, delayMs);
+ }
+ };
+ req.onerror = function () {
+ setTimeout(check, delayMs);
+ }
+ req.open("GET", checkUrl);
+ req.send();
+ }
+
+ setTimeout(check, delayMs);
+</script>
+
+
+<h1><span class="taler">Taler</span> tip available</h1>
+
+<div class="taler-installed-hide">
+ <p>
+ Please select your Taler wallet to pick up the tip.
+ </p>
+</div>
+
+<div>
+ <p>
+ Alternatively, you can scan this QR code to pick up the tip with your mobile wallet:
+ </p>
+ <div class="qr">
+ {{{taler_tip_qrcode_svg}}}
+ </div>
+ <p>
+ Finally, you could click <a href="{{taler_tip_uri}}">this link</a> to
+ try to open your system's Taler wallet if it exists.
+ </p>
+</div>
+<hr />
+</section>
+
+<div class="talerbar">
+ <p>You can learn more about GNU Taler on our <a href="https://taler.net/">website</a>.<br>
+ Copyright &copy; 2014&mdash;2020 Taler Systems SA</p>
+</div>
+</body>
+</html>
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c
index 37dfd884..7ff0aedc 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.c
@@ -855,44 +855,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
}
}
- { /* check for 'Accept' header */
- const char *accept;
-
- accept = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_ACCEPT);
- if (NULL != accept)
- {
- char *a = GNUNET_strdup (accept);
- char *saveptr;
-
- for (char *t = strtok_r (a, ",", &saveptr);
- NULL != t;
- t = strtok_r (NULL, ",", &saveptr))
- {
- char *end;
-
- /* skip leading whitespace */
- while (isspace ((unsigned char) t[0]))
- t++;
- /* trim of ';q=' parameter and everything after space */
- end = strchr (t, ';');
- if (NULL != end)
- *end = '\0';
- end = strchr (t, ' ');
- if (NULL != end)
- *end = '\0';
- if (0 == strcasecmp ("text/html",
- t))
- {
- god->generate_html = true;
- break;
- }
- }
- GNUNET_free (a);
- }
- } /* end check for 'Accept' header */
-
+ god->generate_html = TMH_MHD_test_html_desired (connection);
{
const char *long_poll_timeout_s;
diff --git a/src/backend/taler-merchant-httpd_get-tips-ID.c b/src/backend/taler-merchant-httpd_get-tips-ID.c
index 52a0a561..487e88f4 100644
--- a/src/backend/taler-merchant-httpd_get-tips-ID.c
+++ b/src/backend/taler-merchant-httpd_get-tips-ID.c
@@ -24,6 +24,79 @@
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_get-tips-ID.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_qr.h"
+#include "taler-merchant-httpd_templating.h"
+
+
+/**
+ * Create a taler://tip/ URI for the given @a con and @a tip_id
+ * and @a instance_id.
+ *
+ * @param con HTTP connection
+ * @param tip_id the tip id
+ * @param instance_id instance, may be "default"
+ * @return corresponding taler://tip/ URI, or NULL on missing "host"
+ */
+static char *
+make_taler_tip_uri (struct MHD_Connection *con,
+ const struct GNUNET_HashCode *tip_id,
+ const char *instance_id)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *uri_path;
+ struct GNUNET_Buffer buf = { 0 };
+
+ host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "Host");
+ forwarded_host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+
+ uri_path = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL != forwarded_host)
+ host = forwarded_host;
+
+ if (NULL == host)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ GNUNET_assert (NULL != instance_id);
+ GNUNET_assert (NULL != tip_id);
+
+ GNUNET_buffer_write_str (&buf,
+ "taler");
+ if (GNUNET_NO == TALER_mhd_is_https (con))
+ GNUNET_buffer_write_str (&buf,
+ "+http");
+ GNUNET_buffer_write_str (&buf,
+ "://tip/");
+ GNUNET_buffer_write_str (&buf,
+ host);
+ if (NULL != uri_path)
+ GNUNET_buffer_write_path (&buf,
+ uri_path);
+ if (0 != strcmp ("default",
+ instance_id))
+ {
+ GNUNET_buffer_write_path (&buf,
+ "instances");
+ GNUNET_buffer_write_path (&buf,
+ instance_id);
+ }
+ GNUNET_buffer_write_data_encoded (&buf,
+ tip_id,
+ sizeof (*tip_id));
+ GNUNET_buffer_write_str (&buf,
+ "/");
+ return GNUNET_buffer_reap_str (&buf);
+}
/**
@@ -93,9 +166,9 @@ TMH_get_tips_ID (const struct TMH_RequestHandler *rh,
/* Build response */
{
- MHD_RESULT ret;
struct TALER_Amount remaining;
struct GNUNET_TIME_Absolute expiration_round = expiration;
+ MHD_RESULT ret;
GNUNET_break (0 <=
TALER_amount_subtract (&remaining,
@@ -103,17 +176,67 @@ TMH_get_tips_ID (const struct TMH_RequestHandler *rh,
&total_picked_up));
GNUNET_TIME_round_abs (&expiration_round);
+ if (TMH_MHD_test_html_desired (connection))
+ {
+ char *qr;
+ char *uri;
+
+ uri = make_taler_tip_uri (connection,
+ &tip_id,
+ hc->instance->settings.id);
+ qr = TMH_create_qrcode (uri);
+ if (NULL == qr)
+ {
+ GNUNET_break (0);
+ GNUNET_free (uri);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ALLOCATION_FAILURE,
+ "during QR code generation");
+ }
+ else
+ {
+ struct KVC kvc[] = {
+ { "remaining_tip",
+ TALER_amount2s (&remaining) },
+ { "taler_tip_uri",
+ uri },
+ { "taler_tip_qrcode_svg",
+ qr },
+ { NULL, NULL }
+ };
- ret = TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:s, s:o, s:o}",
- "exchange_url",
- exchange_url,
- "tip_amount",
- TALER_JSON_from_amount (&remaining),
- "expiration",
- GNUNET_JSON_from_time_abs (
- expiration_round));
+ ret = TMH_return_from_template (connection,
+ ( (0 == remaining.value) &&
+ (0 == remaining.fraction) )
+ ? MHD_HTTP_GONE
+ : MHD_HTTP_OK,
+ ( (0 == remaining.value) &&
+ (0 == remaining.fraction) )
+ ? "depleted_tip"
+ : "offer_tip",
+ uri,
+ kvc);
+ }
+ GNUNET_free (uri);
+ GNUNET_free (qr);
+ }
+ else
+ {
+ ret = TALER_MHD_reply_json_pack (connection,
+ ( (0 == remaining.value) &&
+ (0 == remaining.fraction) )
+ ? MHD_HTTP_GONE
+ : MHD_HTTP_OK,
+ "{s:s, s:o, s:o}",
+ "exchange_url",
+ exchange_url,
+ "tip_amount",
+ TALER_JSON_from_amount (&remaining),
+ "expiration",
+ GNUNET_JSON_from_time_abs (
+ expiration_round));
+ }
GNUNET_free (exchange_url);
return ret;
}
diff --git a/src/backend/taler-merchant-httpd_mhd.c b/src/backend/taler-merchant-httpd_mhd.c
index 698c2ac5..754c53b1 100644
--- a/src/backend/taler-merchant-httpd_mhd.c
+++ b/src/backend/taler-merchant-httpd_mhd.c
@@ -71,4 +71,53 @@ TMH_MHD_handler_agpl_redirect (const struct TMH_RequestHandler *rh,
}
+/**
+ * Test if the client requested HTML output.
+ *
+ * @param connection client to test
+ * @return true if HTML was requested
+ */
+bool
+TMH_MHD_test_html_desired (struct MHD_Connection *connection)
+{
+ bool ret;
+ const char *accept;
+
+ accept = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL != accept)
+ {
+ char *a = GNUNET_strdup (accept);
+ char *saveptr;
+
+ for (char *t = strtok_r (a, ",", &saveptr);
+ NULL != t;
+ t = strtok_r (NULL, ",", &saveptr))
+ {
+ char *end;
+
+ /* skip leading whitespace */
+ while (isspace ((unsigned char) t[0]))
+ t++;
+ /* trim of ';q=' parameter and everything after space */
+ end = strchr (t, ';');
+ if (NULL != end)
+ *end = '\0';
+ end = strchr (t, ' ');
+ if (NULL != end)
+ *end = '\0';
+ if (0 == strcasecmp ("text/html",
+ t))
+ {
+ ret = true;
+ break;
+ }
+ }
+ GNUNET_free (a);
+ }
+ return ret;
+}
+
+
/* end of taler-exchange-httpd_mhd.c */
diff --git a/src/backend/taler-merchant-httpd_mhd.h b/src/backend/taler-merchant-httpd_mhd.h
index cbf83add..a3a9afbc 100644
--- a/src/backend/taler-merchant-httpd_mhd.h
+++ b/src/backend/taler-merchant-httpd_mhd.h
@@ -102,4 +102,13 @@ TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh,
struct TMH_MerchantInstance *mi);
+/**
+ * Test if the client requested HTML output.
+ *
+ * @param connection client to test
+ * @return true if HTML was requested
+ */
+bool
+TMH_MHD_test_html_desired (struct MHD_Connection *connection);
+
#endif
diff --git a/src/backend/taler-merchant-httpd_templating.c b/src/backend/taler-merchant-httpd_templating.c
new file mode 100644
index 00000000..50868adb
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_templating.c
@@ -0,0 +1,483 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 merchant/backend/taler-merchant-httpd_templating.c
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_mhd_lib.h>
+#include "taler-merchant-httpd_templating.h"
+#include "../mustach/mustach.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Entry in a key-value array we use to cache templates.
+ */
+struct TVE
+{
+ /**
+ * A name, used as the key. NULL for the last entry.
+ */
+ char *name;
+
+ /**
+ * Language the template is in.
+ */
+ char *lang;
+
+ /**
+ * 0-terminated (!) file data to return for @e name and @e lang.
+ */
+ char *value;
+
+};
+
+
+/**
+ * Array of templates loaded into RAM.
+ */
+static struct TVE *loaded;
+
+/**
+ * Length of the #loaded array.
+ */
+static unsigned int loaded_length;
+
+
+/**
+ * Function called by Mustach to enter the section @a name.
+ * As we do not support sections, we always return 0.
+ *
+ * @param cls a `struct KVC[]` array
+ * @param name section to enter
+ * @return 0 (do not enter)
+ */
+static int
+m_enter (void *cls, const char *name)
+{
+ (void) cls;
+ (void) name;
+ return 0;
+}
+
+
+/**
+ * Function called by mustach to activate the next item in the
+ * section. Does nothing, as we do not support sections.
+ *
+ * @param cls a `struct KVC[]` array
+ * @return 0 (no next item to activate)
+ */
+static int
+m_next (void *cls)
+{
+ (void) cls;
+ return 0;
+}
+
+
+/**
+ * Function called by Mustach to leave the current section.
+ * As we do not support sections, we should never be called.
+ *
+ * @param cls a `struct KVC[]` array
+ * @return 0 (not documented by mustach)
+ */
+static int
+m_leave (void *cls)
+{
+ GNUNET_assert (0);
+ return 0;
+}
+
+
+/**
+ * Return the value of @a name in @a sbuf.
+ *
+ * @param cls a `struct KVC[]` array
+ * @param name the value to lookup
+ * @param[out] sbuf where to return the data
+ * @return mustach-defined status code
+ */
+static int
+m_get (void *cls,
+ const char *name,
+ struct mustach_sbuf *sbuf)
+{
+ const struct KVC *kvc = cls;
+
+ for (unsigned int i = 0; NULL != kvc[i].name; i++)
+ {
+ if (0 == strcmp (name,
+ kvc[i].name))
+ {
+ sbuf->value = kvc[i].value;
+ sbuf->releasecb = NULL;
+ sbuf->closure = NULL;
+ return MUSTACH_OK;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Template requires value for unexpected name `%s'\n",
+ name);
+ return MUSTACH_ERROR_ITEM_NOT_FOUND;
+}
+
+
+/**
+ * Mustach callback at the end. Cleans up the @a cls.
+ *
+ * @param cls a `struct KVC[]` array
+ * @param status status of mustach (ignored)
+ */
+static void
+m_stop (void *cls,
+ int status)
+{
+ (void) cls;
+ (void) status;
+}
+
+
+/**
+ * Our 'universal' callbacks for mustach.
+ */
+static struct mustach_itf itf = {
+ .enter = &m_enter,
+ .next = &m_next,
+ .leave = &m_leave,
+ .get = &m_get,
+ .stop = &m_stop
+};
+
+
+/**
+ * Load Mustach template into memory. Note that we intentionally cache
+ * failures, that is if we ever failed to load a template, we will never try
+ * again.
+ *
+ * @param connection the connection we act upon
+ * @param name name of the template file to load
+ * (MUST be a 'static' string in memory!)
+ * @return NULL on error, otherwise the template
+ */
+static const char *
+lookup_template (struct MHD_Connection *connection,
+ const char *name)
+{
+ struct TVE *best = NULL;
+ const char *lang;
+
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ /* find best match by language */
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ if (0 != strcmp (loaded[i].name,
+ name))
+ continue; /* does not match by name */
+ if ( (NULL == best) ||
+ (TALER_MHD_language_matches (lang,
+ loaded[i].lang) >
+ TALER_MHD_language_matches (lang,
+ best->lang) ) )
+ best = &loaded[i];
+ }
+ if (NULL == best)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No templates found in `%s'\n",
+ name);
+ return NULL;
+ }
+ return best->value;
+}
+
+
+/**
+ * Load a @a template and substitute using @a kvc, returning
+ * the result to the @a connection with the given
+ * @a http_status code.
+ *
+ * @param connection the connection we act upon
+ * @param http_status desired HTTP status code
+ * @param template basename of the template to load
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param kvc key value pairs to fill in
+ * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
+ * #GNUNET_SYSERR on failure (to queue an error)
+ */
+enum GNUNET_GenericReturnValue
+TMH_return_from_template (struct MHD_Connection *connection,
+ unsigned int http_status,
+ const char *template,
+ const char *taler_uri,
+ const struct KVC *kvc)
+{
+ struct MHD_Response *reply;
+ char *body;
+ size_t body_size;
+
+ {
+ const char *tmpl;
+ int eno;
+
+ tmpl = lookup_template (connection,
+ template);
+ if (NULL == tmpl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load template `%s'\n",
+ template);
+ if (MHD_YES !=
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ TALER_EC_MERCHANT_FAILED_TO_LOAD_TEMPLATE,
+ template))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ if (0 !=
+ (eno = mustach (tmpl,
+ &itf,
+ (void *) kvc,
+ &body,
+ &body_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template `%s' with error %d\n",
+ template,
+ eno);
+ if (MHD_YES !=
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_FAILED_TO_EXPAND_TEMPLATE,
+ template))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ }
+
+ /* try to compress reply if client allows it */
+ {
+ bool compressed = false;
+
+ if (MHD_YES ==
+ TALER_MHD_can_compress (connection))
+ {
+ compressed = TALER_MHD_body_compress ((void **) &body,
+ &body_size);
+ }
+ reply = MHD_create_response_from_buffer (body_size,
+ body,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL == reply)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (compressed)
+ {
+ if (MHD_NO ==
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate"))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (reply);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ /* Add standard headers */
+ if (NULL != taler_uri)
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (reply,
+ "Taler",
+ taler_uri));
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/html"));
+
+ /* Actually return reply */
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (connection,
+ http_status,
+ reply);
+ MHD_destroy_response (reply);
+ if (MHD_NO == ret)
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with a template's filename.
+ *
+ * @param cls closure
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static int
+load_template (void *cls,
+ const char *filename)
+{
+ char *lang;
+ char *end;
+ int fd;
+ struct stat sb;
+ char *map;
+ const char *name;
+
+ if ('.' == filename[0])
+ return GNUNET_OK;
+
+ name = strrchr (filename,
+ '/');
+ if (NULL == name)
+ name = filename;
+ else
+ name++;
+ lang = strchr (name,
+ '.');
+ if (NULL == lang)
+ return GNUNET_OK; /* name must include .$LANG */
+ lang++;
+ end = strchr (lang,
+ '.');
+ if ( (NULL == end) ||
+ (0 != strcmp (end,
+ ".must")) )
+ return GNUNET_OK; /* name must end with '.must' */
+
+ /* finally open template */
+ fd = open (filename,
+ O_RDONLY);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ fstat (fd,
+ &sb))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ map = GNUNET_malloc_large (sb.st_size + 1);
+ if (NULL == map)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ if (sb.st_size !=
+ read (fd,
+ map,
+ sb.st_size))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "read",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ GNUNET_break (0 == close (fd));
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ loaded_length + 1);
+ loaded[loaded_length - 1].name = GNUNET_strndup (name,
+ (lang - 1) - name);
+ loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
+ end - lang);
+ loaded[loaded_length - 1].value = map;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Preload templates.
+ */
+int
+TMH_templating_init ()
+{
+ char *dn;
+ int ret;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/merchant/templates/",
+ path);
+ GNUNET_free (path);
+ }
+ ret = GNUNET_DISK_directory_scan (dn,
+ &load_template,
+ NULL);
+ GNUNET_free (dn);
+ if (-1 == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Nicely shut down.
+ */
+void __attribute__ ((destructor))
+get_orders_fini ()
+{
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ GNUNET_free (loaded[i].name);
+ GNUNET_free (loaded[i].lang);
+ GNUNET_free (loaded[i].value);
+ }
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ 0);
+}
diff --git a/src/backend/taler-merchant-httpd_templating.h b/src/backend/taler-merchant-httpd_templating.h
new file mode 100644
index 00000000..e3c9d53e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_templating.h
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 merchant/backend/taler-merchant-httpd_templating.h
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_TEMPLATING_H
+#define TALER_MERCHANT_HTTPD_TEMPLATING_H
+
+#include <microhttpd.h>
+
+/**
+ * Entry in a key-value array we use as the mustach closure.
+ */
+struct KVC
+{
+ /**
+ * A name, used as the key. NULL for the last entry.
+ */
+ const char *name;
+
+ /**
+ * 0-terminated string value to return for @e name.
+ */
+ const char *value;
+};
+
+
+/**
+ * Load a @a template and substitute using @a kvc, returning
+ * the result to the @a connection with the given
+ * @a http_status code.
+ *
+ * @param connection the connection we act upon
+ * @param http_status code to use on success
+ * @param template basename of the template to load
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param kvc key value pairs to fill in
+ * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
+ * #GNUNET_SYSERR on failure (to queue an error)
+ */
+enum GNUNET_GenericReturnValue
+TMH_return_from_template (struct MHD_Connection *connection,
+ unsigned int http_status,
+ const char *template,
+ const char *taler_uri,
+ const struct KVC *kvc);
+
+/**
+ * Preload templates.
+ */
+int
+TMH_templating_init (void);
+
+
+#endif