diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_templating.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_templating.c | 483 |
1 files changed, 483 insertions, 0 deletions
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); +} |