summaryrefslogtreecommitdiff
path: root/src/templating/templating_api.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/templating/templating_api.c')
-rw-r--r--src/templating/templating_api.c524
1 files changed, 524 insertions, 0 deletions
diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c
new file mode 100644
index 000000000..88a17c682
--- /dev/null
+++ b/src/templating/templating_api.c
@@ -0,0 +1,524 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2022 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 templating_api.c
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
+#include "mustach.h"
+#include "mustach-jansson.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;
+
+
+/**
+ * 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_language_matches (lang,
+ loaded[i].lang) >
+ TALER_language_matches (lang,
+ best->lang) ) )
+ best = &loaded[i];
+ }
+ if (NULL == best)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No templates found for `%s'\n",
+ name);
+ return NULL;
+ }
+ return best->value;
+}
+
+
+/**
+ * Get the base URL for static resources.
+ *
+ * @param con the MHD connection
+ * @param instance_id the instance ID
+ * @returns the static files base URL, guaranteed
+ * to have a trailing slash.
+ */
+static char *
+make_static_url (struct MHD_Connection *con,
+ 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);
+
+ if (GNUNET_NO == TALER_mhd_is_https (con))
+ GNUNET_buffer_write_str (&buf,
+ "http://");
+ else
+ GNUNET_buffer_write_str (&buf,
+ "https://");
+ 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_path (&buf,
+ "static/");
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+int
+TALER_TEMPLATING_fill (const char *tmpl,
+ const json_t *root,
+ void **result,
+ size_t *result_size)
+{
+ int eno;
+
+ if (0 !=
+ (eno = mustach_jansson_mem (tmpl,
+ 0, /* length of tmpl */
+ (json_t *) root,
+ Mustach_With_AllExtensions,
+ (char **) result,
+ result_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template with error %d\n",
+ eno);
+ *result = NULL;
+ *result_size = 0;
+ return eno;
+ }
+ return eno;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_build (struct MHD_Connection *connection,
+ unsigned int *http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root,
+ 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);
+ *http_status = MHD_HTTP_NOT_ACCEPTABLE;
+ *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
+ template);
+ return GNUNET_NO;
+ }
+ /* Add default values to the context */
+ if (NULL != instance_id)
+ {
+ char *static_url = make_static_url (connection,
+ instance_id);
+
+ GNUNET_break (0 ==
+ json_object_set_new ((json_t *) root,
+ "static_url",
+ json_string (static_url)));
+ GNUNET_free (static_url);
+ }
+ if (0 !=
+ (eno = mustach_jansson_mem (tmpl,
+ 0,
+ (json_t *) root,
+ Mustach_With_NoExtensions,
+ &body,
+ &body_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template `%s' with error %d\n",
+ template,
+ eno);
+ *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
+ template);
+ 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);
+ *reply = NULL;
+ 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"));
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_reply (struct MHD_Connection *connection,
+ unsigned int http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root)
+{
+ enum GNUNET_GenericReturnValue res;
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+
+ res = TALER_TEMPLATING_build (connection,
+ &http_status,
+ template,
+ instance_id,
+ taler_uri,
+ root,
+ &reply);
+ if (GNUNET_SYSERR == res)
+ return res;
+ ret = MHD_queue_response (connection,
+ http_status,
+ reply);
+ MHD_destroy_response (reply);
+ if (MHD_NO == ret)
+ return GNUNET_SYSERR;
+ return (res == GNUNET_OK)
+ ? GNUNET_OK
+ : GNUNET_NO;
+}
+
+
+/**
+ * Function called with a template's filename.
+ *
+ * @param cls closure, NULL
+ * @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 enum GNUNET_GenericReturnValue
+load_template (void *cls,
+ const char *filename)
+{
+ char *lang;
+ char *end;
+ int fd;
+ struct stat sb;
+ char *map;
+ const char *name;
+
+ (void) cls;
+ 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,
+ "fstat",
+ 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;
+}
+
+
+MHD_RESULT
+TALER_TEMPLATING_reply_error (
+ struct MHD_Connection *connection,
+ const char *template_basename,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ json_t *data;
+ enum GNUNET_GenericReturnValue ret;
+
+ data = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("ec",
+ ec),
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (ec)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail",
+ detail))
+ );
+ ret = TALER_TEMPLATING_reply (connection,
+ http_status,
+ template_basename,
+ NULL,
+ NULL,
+ data);
+ json_decref (data);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ return MHD_YES;
+ case GNUNET_NO:
+ return MHD_YES;
+ case GNUNET_SYSERR:
+ return MHD_NO;
+ }
+ GNUNET_assert (0);
+ return MHD_NO;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_init (const char *subsystem)
+{
+ 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/%s/templates/",
+ path,
+ subsystem);
+ 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;
+}
+
+
+void
+TALER_TEMPLATING_done (void)
+{
+ 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);
+}
+
+
+/* end of templating_api.c */