/* This file is part of TALER Copyright (C) 2020, 2023 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_spa.c * @brief logic to load the single page app (/) * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd_statics.h" #include "taler-merchant-httpd_spa.h" #include /** * Resource from the WebUi. */ struct WebuiFile { /** * Kept in a DLL. */ struct WebuiFile *next; /** * Kept in a DLL. */ struct WebuiFile *prev; /** * Path this resource matches. */ char *path; /** * SPA resource, compressed. */ struct MHD_Response *zspa; /** * SPA resource, vanilla. */ struct MHD_Response *spa; }; /** * Resources of the WebuUI, kept in a DLL. */ static struct WebuiFile *webui_head; /** * Resources of the WebuUI, kept in a DLL. */ static struct WebuiFile *webui_tail; MHD_RESULT TMH_return_spa (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct WebuiFile *w = NULL; const char *infix = hc->infix; if ( (NULL == infix) || (0 == strcmp (infix, "")) ) infix = "index.html"; for (struct WebuiFile *pos = webui_head; NULL != pos; pos = pos->next) if (0 == strcmp (infix, pos->path)) { w = pos; break; } if (NULL == w) return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_GENERIC_ENDPOINT_UNKNOWN, hc->url); if ( (MHD_YES == TALER_MHD_can_compress (connection)) && (NULL != w->zspa) ) return MHD_queue_response (connection, MHD_HTTP_OK, w->zspa); return MHD_queue_response (connection, MHD_HTTP_OK, w->spa); } /** * Function called on each file to load for the WebUI. * * @param cls NULL * @param dn name of the file to load */ static enum GNUNET_GenericReturnValue build_webui (void *cls, const char *dn) { static struct { const char *ext; const char *mime; } mime_map[] = { { .ext = "css", .mime = "text/css" }, { .ext = "html", .mime = "text/html" }, { .ext = "js", .mime = "text/javascript" }, { .ext = "jpg", .mime = "image/jpeg" }, { .ext = "jpeg", .mime = "image/jpeg" }, { .ext = "png", .mime = "image/png" }, { .ext = "svg", .mime = "image/svg+xml" }, { .ext = NULL, .mime = NULL }, }; int fd; struct stat sb; struct MHD_Response *zspa = NULL; struct MHD_Response *spa; const char *ext; const char *mime; (void) cls; /* finally open template */ fd = open (dn, O_RDONLY); if (-1 == fd) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", dn); return GNUNET_SYSERR; } if (0 != fstat (fd, &sb)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", dn); GNUNET_break (0 == close (fd)); return GNUNET_SYSERR; } mime = NULL; ext = strrchr (dn, '.'); if (NULL == ext) { GNUNET_break (0 == close (fd)); return GNUNET_OK; } ext++; for (unsigned int i = 0; NULL != mime_map[i].ext; i++) if (0 == strcasecmp (ext, mime_map[i].ext)) { mime = mime_map[i].mime; break; } { void *in; ssize_t r; size_t csize; in = GNUNET_malloc_large (sb.st_size); if (NULL == in) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "malloc"); GNUNET_break (0 == close (fd)); return GNUNET_SYSERR; } r = read (fd, in, sb.st_size); if ( (-1 == r) || (sb.st_size != (size_t) r) ) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "read", dn); GNUNET_free (in); GNUNET_break (0 == close (fd)); return GNUNET_SYSERR; } csize = (size_t) r; if (MHD_YES == TALER_MHD_body_compress (&in, &csize)) { zspa = MHD_create_response_from_buffer (csize, in, MHD_RESPMEM_MUST_FREE); if (NULL != zspa) { if (MHD_NO == MHD_add_response_header (zspa, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate")) { GNUNET_break (0); MHD_destroy_response (zspa); zspa = NULL; } if (NULL != mime) GNUNET_break (MHD_YES == MHD_add_response_header (zspa, MHD_HTTP_HEADER_CONTENT_TYPE, mime)); } } else { GNUNET_free (in); } } spa = MHD_create_response_from_fd (sb.st_size, fd); if (NULL == spa) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", dn); GNUNET_break (0 == close (fd)); if (NULL != zspa) { MHD_destroy_response (zspa); zspa = NULL; } return GNUNET_SYSERR; } if (NULL != mime) GNUNET_break (MHD_YES == MHD_add_response_header (spa, MHD_HTTP_HEADER_CONTENT_TYPE, mime)); { struct WebuiFile *w; const char *fn; fn = strrchr (dn, '/'); GNUNET_assert (NULL != fn); w = GNUNET_new (struct WebuiFile); w->path = GNUNET_strdup (fn + 1); w->spa = spa; w->zspa = zspa; GNUNET_CONTAINER_DLL_insert (webui_head, webui_tail, w); } return GNUNET_OK; } enum GNUNET_GenericReturnValue TMH_spa_init () { char *dn; { char *path; path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR); if (NULL == path) { GNUNET_break (0); return GNUNET_SYSERR; } GNUNET_asprintf (&dn, "%smerchant/spa/", path); GNUNET_free (path); } if (-1 == GNUNET_DISK_directory_scan (dn, &build_webui, NULL)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load WebUI from `%s'\n", dn); GNUNET_free (dn); return GNUNET_SYSERR; } GNUNET_free (dn); return GNUNET_OK; } /** * Nicely shut down. */ void __attribute__ ((destructor)) get_spa_fini () { struct WebuiFile *w; while (NULL != (w = webui_head)) { GNUNET_CONTAINER_DLL_remove (webui_head, webui_tail, w); if (NULL != w->spa) { MHD_destroy_response (w->spa); w->spa = NULL; } if (NULL != w->zspa) { MHD_destroy_response (w->zspa); w->zspa = NULL; } GNUNET_free (w->path); GNUNET_free (w); } }