/* 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_statics.c * @brief logic to load and complete HTML templates * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd_statics.h" #include "../mustach/mustach.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; /** * Pre-built reply. */ struct MHD_Response *reply; }; /** * 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 struct TVE * lookup_file (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 static file found for `%s'\n", name); return NULL; } return best; } MHD_RESULT TMH_return_static (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { const struct TVE *tmpl; tmpl = lookup_file (connection, hc->infix); if (NULL == tmpl) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load static file `%s'\n", hc->infix); return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_GENERIC_ENDPOINT_UNKNOWN, hc->infix); } return MHD_queue_response (connection, MHD_HTTP_OK, tmpl->reply); } /** * Function called with a static file'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_static_file (void *cls, const char *filename) { char *lang; char *end; int fd; struct stat sb; const char *name; struct MHD_Response *reply; 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 _some_ extension */ lang++; end = strchr (lang, '.'); if (NULL == end) { /* language was not present, we ONLY have the extension */ end = lang; lang = NULL; } /* 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; } reply = MHD_create_response_from_fd (sb.st_size, fd); if (NULL == reply) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", filename); GNUNET_break (0 == close (fd)); return GNUNET_OK; } { static struct MimeMap { const char *ext; const char *mime; } mm[] = { { .ext = ".css", .mime = "text/css" }, { .ext = ".js", .mime = "text/javascript" }, { .ext = ".html", .mime = "text/html" }, { .ext = ".htm", .mime = "text/html" }, { .ext = ".txt", .mime = "text/plain" }, { .ext = ".pdf", .mime = "application/pdf" }, { .ext = ".jpg", .mime = "image/jpeg" }, { .ext = ".jpeg", .mime = "image/jpeg" }, { .ext = ".png", .mime = "image/png" }, { .ext = ".apng", .mime = "image/apng" }, { .ext = ".gif", .mime = "image/gif" }, { .ext = ".svg", .mime = "image/svg+xml" }, { .ext = ".tiff", .mime = "image/tiff" }, { .ext = ".ico", .mime = "image/x-icon" }, { .ext = ".bmp", .mime = "image/bmp" }, { .ext = ".epub", .mime = "application/epub+zip" }, { .ext = ".xml", .mime = "text/xml" }, { .ext = NULL, .mime = NULL } }; const char *mime; mime = NULL; for (unsigned int i = 0; NULL != mm[i].ext; i++) if (0 == strcasecmp (mm[i].ext, end)) { mime = mm[i].mime; break; } if (NULL != mime) GNUNET_break (MHD_NO != MHD_add_response_header (reply, MHD_HTTP_HEADER_CONTENT_TYPE, mime)); } GNUNET_array_grow (loaded, loaded_length, loaded_length + 1); if (NULL != lang) { GNUNET_asprintf (&loaded[loaded_length - 1].name, "%.*s%s", (int) (lang - name) - 1, name, end); loaded[loaded_length - 1].lang = GNUNET_strndup (lang, end - lang); } else { loaded[loaded_length - 1].name = GNUNET_strdup (name); } loaded[loaded_length - 1].reply = reply; return GNUNET_OK; } /** * Preload static files. */ int TMH_statics_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/static/", path); GNUNET_free (path); } ret = GNUNET_DISK_directory_scan (dn, &load_static_file, NULL); GNUNET_free (dn); if (-1 == ret) { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Nicely shut down. */ void __attribute__ ((destructor)) get_statics_fini () { for (unsigned int i = 0; i