/* 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 */ /** * @file taler-merchant-httpd_templating.c * @brief logic to load and complete HTML templates * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd_templating.h" #include "../mustach/mustach.h" #include "../mustach/mustach-jansson.h" #include /** * 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 TALER_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 root, 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 root JSON object to pass as the root context * @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, 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); if (MHD_YES != TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_ACCEPTABLE, TALER_EC_MERCHANT_GENERIC_FAILED_TO_LOAD_TEMPLATE, template)) return GNUNET_SYSERR; return GNUNET_NO; } if (0 != (eno = mustach_jansson (tmpl, root, &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_GENERIC_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)) templating_fini () { for (unsigned int i = 0; i