LCOV - code coverage report
Current view: top level - templating - templating_api.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 44.3 % 192 85
Test Date: 2026-09-09 15:11:34 Functions: 50.0 % 10 5

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2020, 2022 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file templating_api.c
      18              :  * @brief logic to load and complete HTML templates
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"  /* UNNECESSARY? */
      22              : #include <gnunet/gnunet_util_lib.h>
      23              : #include "taler/taler_util.h"
      24              : #include "taler/taler_mhd_lib.h"
      25              : #include "taler/taler_templating_lib.h"
      26              : #include "mustach.h"
      27              : #include "mustach-jansson.h"
      28              : #include <gnunet/gnunet_mhd_compat.h>
      29              : 
      30              : 
      31              : /**
      32              :  * Entry in a key-value array we use to cache templates.
      33              :  */
      34              : struct TVE
      35              : {
      36              :   /**
      37              :    * A name, used as the key. NULL for the last entry.
      38              :    */
      39              :   char *name;
      40              : 
      41              :   /**
      42              :    * Language the template is in.
      43              :    */
      44              :   char *lang;
      45              : 
      46              :   /**
      47              :    * 0-terminated (!) file data to return for @e name and @e lang.
      48              :    */
      49              :   char *value;
      50              : 
      51              : };
      52              : 
      53              : 
      54              : /**
      55              :  * Array of templates loaded into RAM.
      56              :  */
      57              : static struct TVE *loaded;
      58              : 
      59              : /**
      60              :  * Length of the #loaded array.
      61              :  */
      62              : static unsigned int loaded_length;
      63              : 
      64              : 
      65              : /**
      66              :  * Load Mustach template into memory.  Note that we intentionally cache
      67              :  * failures, that is if we ever failed to load a template, we will never try
      68              :  * again.
      69              :  *
      70              :  * @param connection the connection we act upon
      71              :  * @param name name of the template file to load
      72              :  *        (MUST be a 'static' string in memory!)
      73              :  * @return NULL on error, otherwise the template
      74              :  */
      75              : static struct TVE *
      76            3 : lookup_template (struct MHD_Connection *connection,
      77              :                  const char *name)
      78              : {
      79            3 :   struct TVE *best = NULL;
      80            3 :   struct TVE *fallback = NULL;
      81            3 :   double best_q = 0.0;
      82              :   const char *lang;
      83              : 
      84            3 :   lang = MHD_lookup_connection_value (connection,
      85              :                                       MHD_HEADER_KIND,
      86              :                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
      87            3 :   if (NULL == lang)
      88            3 :     lang = "en";
      89              :   /* find best match by language */
      90           63 :   for (unsigned int i = 0; i<loaded_length; i++)
      91              :   {
      92              :     double q;
      93              : 
      94           60 :     if (0 != strcmp (loaded[i].name,
      95              :                      name))
      96           57 :       continue; /* does not match by name */
      97            3 :     if (NULL == loaded[i].lang) /* no language == always best match */
      98            0 :       return &loaded[i];
      99            3 :     if (NULL == fallback)
     100            3 :       fallback = &loaded[i]; /* in case nothing matches the language */
     101            3 :     q = TALER_pattern_matches (lang,
     102            3 :                                loaded[i].lang);
     103            3 :     if (q <= best_q)
     104            0 :       continue; /* not a strictly better match than what we have */
     105            3 :     best_q = q;
     106            3 :     best = &loaded[i];
     107              :   }
     108            3 :   if (NULL == best)
     109            0 :     best = fallback; /* no variant matched the language, serve any */
     110            3 :   if (NULL == best)
     111              :   {
     112            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     113              :                 "No templates found for `%s'\n",
     114              :                 name);
     115            0 :     return NULL;
     116              :   }
     117            3 :   return best;
     118              : }
     119              : 
     120              : 
     121              : /**
     122              :  * Get the base URL for static resources.
     123              :  *
     124              :  * @param con the MHD connection
     125              :  * @param instance_id the instance ID
     126              :  * @returns the static files base URL, guaranteed
     127              :  *          to have a trailing slash.
     128              :  */
     129              : static char *
     130            0 : make_static_url (struct MHD_Connection *con,
     131              :                  const char *instance_id)
     132              : {
     133              :   const char *host;
     134              :   const char *forwarded_host;
     135              :   const char *uri_path;
     136            0 :   struct GNUNET_Buffer buf = { 0 };
     137              : 
     138            0 :   host = MHD_lookup_connection_value (con,
     139              :                                       MHD_HEADER_KIND,
     140              :                                       "Host");
     141            0 :   forwarded_host = MHD_lookup_connection_value (con,
     142              :                                                 MHD_HEADER_KIND,
     143              :                                                 "X-Forwarded-Host");
     144              : 
     145            0 :   uri_path = MHD_lookup_connection_value (con,
     146              :                                           MHD_HEADER_KIND,
     147              :                                           "X-Forwarded-Prefix");
     148            0 :   if (NULL != forwarded_host)
     149            0 :     host = forwarded_host;
     150              : 
     151            0 :   if (NULL == host)
     152              :   {
     153            0 :     GNUNET_break (0);
     154            0 :     return NULL;
     155              :   }
     156              : 
     157            0 :   GNUNET_assert (NULL != instance_id);
     158              : 
     159            0 :   if (GNUNET_NO == TALER_mhd_is_https (con))
     160            0 :     GNUNET_buffer_write_str (&buf,
     161              :                              "http://");
     162              :   else
     163            0 :     GNUNET_buffer_write_str (&buf,
     164              :                              "https://");
     165            0 :   GNUNET_buffer_write_str (&buf,
     166              :                            host);
     167            0 :   if (NULL != uri_path)
     168            0 :     GNUNET_buffer_write_path (&buf,
     169              :                               uri_path);
     170            0 :   if (0 != strcmp ("default",
     171              :                    instance_id))
     172              :   {
     173            0 :     GNUNET_buffer_write_path (&buf,
     174              :                               "instances");
     175            0 :     GNUNET_buffer_write_path (&buf,
     176              :                               instance_id);
     177              :   }
     178            0 :   GNUNET_buffer_write_path (&buf,
     179              :                             "static/");
     180            0 :   return GNUNET_buffer_reap_str (&buf);
     181              : }
     182              : 
     183              : 
     184              : int
     185            0 : TALER_TEMPLATING_fill (const char *tmpl,
     186              :                        const json_t *root,
     187              :                        void **result,
     188              :                        size_t *result_size)
     189              : {
     190              :   int eno;
     191              : 
     192            0 :   if (0 !=
     193            0 :       (eno = mustach_jansson_mem (tmpl,
     194              :                                   0, /* length of tmpl */
     195              :                                   (json_t *) root,
     196              :                                   Mustach_With_AllExtensions,
     197              :                                   (char **) result,
     198              :                                   result_size)))
     199              :   {
     200            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     201              :                 "mustach failed on template with error %d\n",
     202              :                 eno);
     203            0 :     *result = NULL;
     204            0 :     *result_size = 0;
     205            0 :     return eno;
     206              :   }
     207            0 :   return eno;
     208              : }
     209              : 
     210              : 
     211              : int
     212            0 : TALER_TEMPLATING_fill2 (const void *tmpl,
     213              :                         size_t tmpl_len,
     214              :                         const json_t *root,
     215              :                         void **result,
     216              :                         size_t *result_size)
     217              : {
     218              :   int eno;
     219              : 
     220            0 :   if (0 !=
     221            0 :       (eno = mustach_jansson_mem (tmpl,
     222              :                                   tmpl_len,
     223              :                                   (json_t *) root,
     224              :                                   Mustach_With_AllExtensions,
     225              :                                   (char **) result,
     226              :                                   result_size)))
     227              :   {
     228            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     229              :                 "mustach failed on template with error %d\n",
     230              :                 eno);
     231            0 :     *result = NULL;
     232            0 :     *result_size = 0;
     233            0 :     return eno;
     234              :   }
     235            0 :   return eno;
     236              : }
     237              : 
     238              : 
     239              : enum GNUNET_GenericReturnValue
     240            3 : TALER_TEMPLATING_build (struct MHD_Connection *connection,
     241              :                         unsigned int *http_status,
     242              :                         const char *template,
     243              :                         const char *instance_id,
     244              :                         const char *taler_uri,
     245              :                         const json_t *root,
     246              :                         struct MHD_Response **reply)
     247              : {
     248              :   char *body;
     249              :   size_t body_size;
     250              :   struct TVE *tve;
     251              : 
     252              :   {
     253              :     const char *tmpl;
     254              :     int eno;
     255              : 
     256            3 :     tve = lookup_template (connection,
     257              :                            template);
     258            3 :     if (NULL == tve)
     259              :     {
     260            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     261              :                   "Failed to load template `%s'\n",
     262              :                   template);
     263            0 :       *http_status = MHD_HTTP_NOT_ACCEPTABLE;
     264            0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
     265              :                                      template);
     266            0 :       return GNUNET_NO;
     267              :     }
     268            3 :     tmpl = tve->value;
     269              :     /* Add default values to the context */
     270            3 :     if (NULL != instance_id)
     271              :     {
     272            0 :       char *static_url = make_static_url (connection,
     273              :                                           instance_id);
     274              : 
     275            0 :       if (NULL != static_url)
     276              :       {
     277            0 :         GNUNET_break (0 ==
     278              :                       json_object_set_new ((json_t *) root,
     279              :                                            "static_url",
     280              :                                            json_string (static_url)));
     281            0 :         GNUNET_free (static_url);
     282              :       }
     283              :     }
     284            3 :     if (0 !=
     285            3 :         (eno = mustach_jansson_mem (tmpl,
     286              :                                     0,
     287              :                                     (json_t *) root,
     288              :                                     Mustach_With_NoExtensions,
     289              :                                     &body,
     290              :                                     &body_size)))
     291              :     {
     292            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     293              :                   "mustach failed on template `%s' with error %d\n",
     294              :                   template,
     295              :                   eno);
     296            0 :       *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     297            0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
     298              :                                      template);
     299            0 :       return GNUNET_NO;
     300              :     }
     301              :   }
     302              : 
     303              :   /* try to compress reply if client allows it */
     304              :   {
     305            3 :     bool compressed = false;
     306              : 
     307            3 :     if (TALER_MHD_CT_DEFLATE ==
     308            3 :         TALER_MHD_can_compress (connection,
     309              :                                 TALER_MHD_CT_DEFLATE))
     310              :     {
     311            3 :       compressed = TALER_MHD_body_compress ((void **) &body,
     312              :                                             &body_size);
     313              :     }
     314            3 :     *reply = MHD_create_response_from_buffer (body_size,
     315              :                                               body,
     316              :                                               MHD_RESPMEM_MUST_FREE);
     317            3 :     if (NULL == *reply)
     318              :     {
     319            0 :       GNUNET_free (body);
     320            0 :       GNUNET_break (0);
     321            0 :       return GNUNET_SYSERR;
     322              :     }
     323            3 :     if (compressed)
     324              :     {
     325            3 :       if (MHD_NO ==
     326            3 :           MHD_add_response_header (*reply,
     327              :                                    MHD_HTTP_HEADER_CONTENT_ENCODING,
     328              :                                    "deflate"))
     329              :       {
     330            0 :         GNUNET_break (0);
     331            0 :         MHD_destroy_response (*reply);
     332            0 :         *reply = NULL;
     333            0 :         return GNUNET_SYSERR;
     334              :       }
     335              :     }
     336              :   }
     337            3 :   if (NULL != tve->lang)
     338            3 :     GNUNET_break (MHD_YES ==
     339              :                   MHD_add_response_header (*reply,
     340              :                                            MHD_HTTP_HEADER_CONTENT_LANGUAGE,
     341              :                                            tve->lang));
     342              : 
     343              :   /* Add standard headers */
     344            3 :   if (NULL != taler_uri)
     345            0 :     GNUNET_break (MHD_NO !=
     346              :                   MHD_add_response_header (*reply,
     347              :                                            "Taler",
     348              :                                            taler_uri));
     349            3 :   return GNUNET_OK;
     350              : }
     351              : 
     352              : 
     353              : enum GNUNET_GenericReturnValue
     354            0 : TALER_TEMPLATING_reply (struct MHD_Connection *connection,
     355              :                         unsigned int http_status,
     356              :                         const char *template,
     357              :                         const char *instance_id,
     358              :                         const char *taler_uri,
     359              :                         const json_t *root)
     360              : {
     361              :   enum GNUNET_GenericReturnValue res;
     362              :   struct MHD_Response *reply;
     363              :   enum MHD_Result ret;
     364              : 
     365            0 :   res = TALER_TEMPLATING_build (connection,
     366              :                                 &http_status,
     367              :                                 template,
     368              :                                 instance_id,
     369              :                                 taler_uri,
     370              :                                 root,
     371              :                                 &reply);
     372            0 :   if (GNUNET_SYSERR == res)
     373            0 :     return res;
     374            0 :   if (GNUNET_OK == res)
     375            0 :     GNUNET_break (MHD_NO !=
     376              :                   MHD_add_response_header (reply,
     377              :                                            MHD_HTTP_HEADER_CONTENT_TYPE,
     378              :                                            "text/html"));
     379              :   /* Note: on failure, @a reply is a JSON error message
     380              :      which already comes with its own content type. */
     381              :   /* FIXME: also vary by compression? */
     382            0 :   GNUNET_break (MHD_NO !=
     383              :                 MHD_add_response_header (
     384              :                   reply,
     385              :                   MHD_HTTP_HEADER_VARY,
     386              :                   MHD_HTTP_HEADER_ACCEPT_LANGUAGE));
     387            0 :   ret = MHD_queue_response (connection,
     388              :                             http_status,
     389              :                             reply);
     390            0 :   MHD_destroy_response (reply);
     391            0 :   if (MHD_NO == ret)
     392            0 :     return GNUNET_SYSERR;
     393              :   return (res == GNUNET_OK)
     394              :     ? GNUNET_OK
     395            0 :     : GNUNET_NO;
     396              : }
     397              : 
     398              : 
     399              : /**
     400              :  * Function called with a template's filename.
     401              :  *
     402              :  * @param cls closure, NULL
     403              :  * @param filename complete filename (absolute path)
     404              :  * @return #GNUNET_OK to continue to iterate,
     405              :  *  #GNUNET_NO to stop iteration with no error,
     406              :  *  #GNUNET_SYSERR to abort iteration with error!
     407              :  */
     408              : static enum GNUNET_GenericReturnValue
     409          360 : load_template (void *cls,
     410              :                const char *filename)
     411              : {
     412              :   const char *lang;
     413              :   const char *end;
     414              :   int fd;
     415              :   struct stat sb;
     416              :   char *map;
     417              :   const char *name;
     418              : 
     419              :   (void) cls;
     420          360 :   if ('.' == filename[0])
     421            0 :     return GNUNET_OK;
     422          360 :   name = strrchr (filename,
     423              :                   '/');
     424          360 :   if (NULL == name)
     425            0 :     name = filename;
     426              :   else
     427          360 :     name++;
     428          360 :   lang = strchr (name,
     429              :                  '.');
     430          360 :   if (NULL == lang)
     431            0 :     return GNUNET_OK; /* name must include .$LANG */
     432          360 :   lang++;
     433          360 :   end = strchr (lang,
     434              :                 '.');
     435          360 :   if ( (NULL == end) ||
     436          360 :        (0 != strcmp (end,
     437              :                      ".must")) )
     438            0 :     return GNUNET_OK; /* name must end with '.must' */
     439              : 
     440              :   /* finally open template */
     441          360 :   fd = open (filename,
     442              :              O_RDONLY);
     443          360 :   if (-1 == fd)
     444              :   {
     445            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     446              :                               "open",
     447              :                               filename);
     448              : 
     449            0 :     return GNUNET_SYSERR;
     450              :   }
     451          360 :   if (0 !=
     452          360 :       fstat (fd,
     453              :              &sb))
     454              :   {
     455            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     456              :                               "fstat",
     457              :                               filename);
     458            0 :     GNUNET_break (0 == close (fd));
     459            0 :     return GNUNET_OK;
     460              :   }
     461          360 :   map = GNUNET_malloc_large (sb.st_size + 1);
     462          360 :   if (NULL == map)
     463              :   {
     464            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     465              :                          "malloc");
     466            0 :     GNUNET_break (0 == close (fd));
     467            0 :     return GNUNET_SYSERR;
     468              :   }
     469          360 :   if (sb.st_size !=
     470          360 :       read (fd,
     471              :             map,
     472          360 :             sb.st_size))
     473              :   {
     474            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     475              :                               "read",
     476              :                               filename);
     477            0 :     GNUNET_free (map);
     478            0 :     GNUNET_break (0 == close (fd));
     479            0 :     return GNUNET_OK;
     480              :   }
     481          360 :   GNUNET_break (0 == close (fd));
     482          360 :   GNUNET_array_grow (loaded,
     483              :                      loaded_length,
     484              :                      loaded_length + 1);
     485          360 :   loaded[loaded_length - 1].name = GNUNET_strndup (name,
     486              :                                                    (lang - 1) - name);
     487          360 :   loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
     488              :                                                    end - lang);
     489          360 :   loaded[loaded_length - 1].value = map;
     490          360 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     491              :               "Loading template `%s' (%s)\n",
     492              :               filename,
     493              :               loaded[loaded_length - 1].name);
     494          360 :   return GNUNET_OK;
     495              : }
     496              : 
     497              : 
     498              : enum MHD_Result
     499            0 : TALER_TEMPLATING_reply_error (
     500              :   struct MHD_Connection *connection,
     501              :   const char *template_basename,
     502              :   unsigned int http_status,
     503              :   enum TALER_ErrorCode ec,
     504              :   const char *detail)
     505              : {
     506              :   json_t *data;
     507              :   enum GNUNET_GenericReturnValue ret;
     508              : 
     509            0 :   data = GNUNET_JSON_PACK (
     510              :     GNUNET_JSON_pack_uint64 ("ec",
     511              :                              ec),
     512              :     GNUNET_JSON_pack_string ("hint",
     513              :                              TALER_ErrorCode_get_hint (ec)),
     514              :     GNUNET_JSON_pack_allow_null (
     515              :       GNUNET_JSON_pack_string ("detail",
     516              :                                detail))
     517              :     );
     518            0 :   ret = TALER_TEMPLATING_reply (connection,
     519              :                                 http_status,
     520              :                                 template_basename,
     521              :                                 NULL,
     522              :                                 NULL,
     523              :                                 data);
     524            0 :   json_decref (data);
     525            0 :   switch (ret)
     526              :   {
     527            0 :   case GNUNET_OK:
     528            0 :     return MHD_YES;
     529            0 :   case GNUNET_NO:
     530            0 :     return MHD_YES;
     531            0 :   case GNUNET_SYSERR:
     532            0 :     return MHD_NO;
     533              :   }
     534            0 :   GNUNET_assert (0);
     535              :   return MHD_NO;
     536              : }
     537              : 
     538              : 
     539              : enum GNUNET_GenericReturnValue
     540           18 : TALER_TEMPLATING_init (const struct GNUNET_OS_ProjectData *pd)
     541              : {
     542              :   char *dn;
     543              :   int ret;
     544              : 
     545              :   {
     546              :     char *path;
     547              : 
     548           18 :     path = GNUNET_OS_installation_get_path (pd,
     549              :                                             GNUNET_OS_IPK_DATADIR);
     550           18 :     if (NULL == path)
     551              :     {
     552            0 :       GNUNET_break (0);
     553            0 :       return GNUNET_SYSERR;
     554              :     }
     555           18 :     GNUNET_asprintf (&dn,
     556              :                      "%s/templates/",
     557              :                      path);
     558           18 :     GNUNET_free (path);
     559              :   }
     560           18 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     561              :               "Loading templates from `%s'\n",
     562              :               dn);
     563           18 :   ret = GNUNET_DISK_directory_scan (dn,
     564              :                                     &load_template,
     565              :                                     NULL);
     566           18 :   GNUNET_free (dn);
     567           18 :   if (-1 == ret)
     568              :   {
     569            0 :     GNUNET_break (0);
     570            0 :     return GNUNET_SYSERR;
     571              :   }
     572           18 :   return GNUNET_OK;
     573              : }
     574              : 
     575              : 
     576              : void
     577           18 : TALER_TEMPLATING_done (void)
     578              : {
     579          378 :   for (unsigned int i = 0; i<loaded_length; i++)
     580              :   {
     581          360 :     GNUNET_free (loaded[i].name);
     582          360 :     GNUNET_free (loaded[i].lang);
     583          360 :     GNUNET_free (loaded[i].value);
     584              :   }
     585           18 :   GNUNET_array_grow (loaded,
     586              :                      loaded_length,
     587              :                      0);
     588           18 : }
     589              : 
     590              : 
     591              : /* end of templating_api.c */
        

Generated by: LCOV version 2.0-1