LCOV - code coverage report
Current view: top level - templating - templating_api.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 43.6 % 179 78
Test Date: 2026-04-14 15:39:31 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 "taler/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 const char *
      76            3 : lookup_template (struct MHD_Connection *connection,
      77              :                  const char *name)
      78              : {
      79            3 :   struct TVE *best = NULL;
      80            3 :   double best_q = 0.0;
      81              :   const char *lang;
      82              : 
      83            3 :   lang = MHD_lookup_connection_value (connection,
      84              :                                       MHD_HEADER_KIND,
      85              :                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
      86            3 :   if (NULL == lang)
      87            3 :     lang = "en";
      88              :   /* find best match by language */
      89           63 :   for (unsigned int i = 0; i<loaded_length; i++)
      90              :   {
      91              :     double q;
      92              : 
      93           60 :     if (0 != strcmp (loaded[i].name,
      94              :                      name))
      95           57 :       continue; /* does not match by name */
      96            3 :     if (NULL == loaded[i].lang) /* no language == always best match */
      97            0 :       return loaded[i].value;
      98            3 :     q = TALER_pattern_matches (lang,
      99            3 :                                loaded[i].lang);
     100            3 :     if (q < best_q)
     101            0 :       continue;
     102            3 :     best_q = q;
     103            3 :     best = &loaded[i];
     104              :   }
     105            3 :   if (NULL == best)
     106              :   {
     107            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     108              :                 "No templates found for `%s'\n",
     109              :                 name);
     110            0 :     return NULL;
     111              :   }
     112            3 :   return best->value;
     113              : }
     114              : 
     115              : 
     116              : /**
     117              :  * Get the base URL for static resources.
     118              :  *
     119              :  * @param con the MHD connection
     120              :  * @param instance_id the instance ID
     121              :  * @returns the static files base URL, guaranteed
     122              :  *          to have a trailing slash.
     123              :  */
     124              : static char *
     125            0 : make_static_url (struct MHD_Connection *con,
     126              :                  const char *instance_id)
     127              : {
     128              :   const char *host;
     129              :   const char *forwarded_host;
     130              :   const char *uri_path;
     131            0 :   struct GNUNET_Buffer buf = { 0 };
     132              : 
     133            0 :   host = MHD_lookup_connection_value (con,
     134              :                                       MHD_HEADER_KIND,
     135              :                                       "Host");
     136            0 :   forwarded_host = MHD_lookup_connection_value (con,
     137              :                                                 MHD_HEADER_KIND,
     138              :                                                 "X-Forwarded-Host");
     139              : 
     140            0 :   uri_path = MHD_lookup_connection_value (con,
     141              :                                           MHD_HEADER_KIND,
     142              :                                           "X-Forwarded-Prefix");
     143            0 :   if (NULL != forwarded_host)
     144            0 :     host = forwarded_host;
     145              : 
     146            0 :   if (NULL == host)
     147              :   {
     148            0 :     GNUNET_break (0);
     149            0 :     return NULL;
     150              :   }
     151              : 
     152            0 :   GNUNET_assert (NULL != instance_id);
     153              : 
     154            0 :   if (GNUNET_NO == TALER_mhd_is_https (con))
     155            0 :     GNUNET_buffer_write_str (&buf,
     156              :                              "http://");
     157              :   else
     158            0 :     GNUNET_buffer_write_str (&buf,
     159              :                              "https://");
     160            0 :   GNUNET_buffer_write_str (&buf,
     161              :                            host);
     162            0 :   if (NULL != uri_path)
     163            0 :     GNUNET_buffer_write_path (&buf,
     164              :                               uri_path);
     165            0 :   if (0 != strcmp ("default",
     166              :                    instance_id))
     167              :   {
     168            0 :     GNUNET_buffer_write_path (&buf,
     169              :                               "instances");
     170            0 :     GNUNET_buffer_write_path (&buf,
     171              :                               instance_id);
     172              :   }
     173            0 :   GNUNET_buffer_write_path (&buf,
     174              :                             "static/");
     175            0 :   return GNUNET_buffer_reap_str (&buf);
     176              : }
     177              : 
     178              : 
     179              : int
     180            0 : TALER_TEMPLATING_fill (const char *tmpl,
     181              :                        const json_t *root,
     182              :                        void **result,
     183              :                        size_t *result_size)
     184              : {
     185              :   int eno;
     186              : 
     187            0 :   if (0 !=
     188            0 :       (eno = mustach_jansson_mem (tmpl,
     189              :                                   0, /* length of tmpl */
     190              :                                   (json_t *) root,
     191              :                                   Mustach_With_AllExtensions,
     192              :                                   (char **) result,
     193              :                                   result_size)))
     194              :   {
     195            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     196              :                 "mustach failed on template with error %d\n",
     197              :                 eno);
     198            0 :     *result = NULL;
     199            0 :     *result_size = 0;
     200            0 :     return eno;
     201              :   }
     202            0 :   return eno;
     203              : }
     204              : 
     205              : 
     206              : int
     207            0 : TALER_TEMPLATING_fill2 (const void *tmpl,
     208              :                         size_t tmpl_len,
     209              :                         const json_t *root,
     210              :                         void **result,
     211              :                         size_t *result_size)
     212              : {
     213              :   int eno;
     214              : 
     215            0 :   if (0 !=
     216            0 :       (eno = mustach_jansson_mem (tmpl,
     217              :                                   tmpl_len,
     218              :                                   (json_t *) root,
     219              :                                   Mustach_With_AllExtensions,
     220              :                                   (char **) result,
     221              :                                   result_size)))
     222              :   {
     223            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     224              :                 "mustach failed on template with error %d\n",
     225              :                 eno);
     226            0 :     *result = NULL;
     227            0 :     *result_size = 0;
     228            0 :     return eno;
     229              :   }
     230            0 :   return eno;
     231              : }
     232              : 
     233              : 
     234              : enum GNUNET_GenericReturnValue
     235            3 : TALER_TEMPLATING_build (struct MHD_Connection *connection,
     236              :                         unsigned int *http_status,
     237              :                         const char *template,
     238              :                         const char *instance_id,
     239              :                         const char *taler_uri,
     240              :                         const json_t *root,
     241              :                         struct MHD_Response **reply)
     242              : {
     243              :   char *body;
     244              :   size_t body_size;
     245              : 
     246              :   {
     247              :     const char *tmpl;
     248              :     int eno;
     249              : 
     250            3 :     tmpl = lookup_template (connection,
     251              :                             template);
     252            3 :     if (NULL == tmpl)
     253              :     {
     254            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     255              :                   "Failed to load template `%s'\n",
     256              :                   template);
     257            0 :       *http_status = MHD_HTTP_NOT_ACCEPTABLE;
     258            0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
     259              :                                      template);
     260            0 :       return GNUNET_NO;
     261              :     }
     262              :     /* Add default values to the context */
     263            3 :     if (NULL != instance_id)
     264              :     {
     265            0 :       char *static_url = make_static_url (connection,
     266              :                                           instance_id);
     267              : 
     268            0 :       GNUNET_break (0 ==
     269              :                     json_object_set_new ((json_t *) root,
     270              :                                          "static_url",
     271              :                                          json_string (static_url)));
     272            0 :       GNUNET_free (static_url);
     273              :     }
     274            3 :     if (0 !=
     275            3 :         (eno = mustach_jansson_mem (tmpl,
     276              :                                     0,
     277              :                                     (json_t *) root,
     278              :                                     Mustach_With_NoExtensions,
     279              :                                     &body,
     280              :                                     &body_size)))
     281              :     {
     282            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     283              :                   "mustach failed on template `%s' with error %d\n",
     284              :                   template,
     285              :                   eno);
     286            0 :       *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     287            0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
     288              :                                      template);
     289            0 :       return GNUNET_NO;
     290              :     }
     291              :   }
     292              : 
     293              :   /* try to compress reply if client allows it */
     294              :   {
     295            3 :     bool compressed = false;
     296              : 
     297            3 :     if (TALER_MHD_CT_DEFLATE ==
     298            3 :         TALER_MHD_can_compress (connection,
     299              :                                 TALER_MHD_CT_DEFLATE))
     300              :     {
     301            3 :       compressed = TALER_MHD_body_compress ((void **) &body,
     302              :                                             &body_size);
     303              :     }
     304            3 :     *reply = MHD_create_response_from_buffer (body_size,
     305              :                                               body,
     306              :                                               MHD_RESPMEM_MUST_FREE);
     307            3 :     if (NULL == *reply)
     308              :     {
     309            0 :       GNUNET_break (0);
     310            0 :       return GNUNET_SYSERR;
     311              :     }
     312            3 :     if (compressed)
     313              :     {
     314            3 :       if (MHD_NO ==
     315            3 :           MHD_add_response_header (*reply,
     316              :                                    MHD_HTTP_HEADER_CONTENT_ENCODING,
     317              :                                    "deflate"))
     318              :       {
     319            0 :         GNUNET_break (0);
     320            0 :         MHD_destroy_response (*reply);
     321            0 :         *reply = NULL;
     322            0 :         return GNUNET_SYSERR;
     323              :       }
     324              :     }
     325              :   }
     326              : 
     327              :   /* Add standard headers */
     328            3 :   if (NULL != taler_uri)
     329            0 :     GNUNET_break (MHD_NO !=
     330              :                   MHD_add_response_header (*reply,
     331              :                                            "Taler",
     332              :                                            taler_uri));
     333            3 :   return GNUNET_OK;
     334              : }
     335              : 
     336              : 
     337              : enum GNUNET_GenericReturnValue
     338            0 : TALER_TEMPLATING_reply (struct MHD_Connection *connection,
     339              :                         unsigned int http_status,
     340              :                         const char *template,
     341              :                         const char *instance_id,
     342              :                         const char *taler_uri,
     343              :                         const json_t *root)
     344              : {
     345              :   enum GNUNET_GenericReturnValue res;
     346              :   struct MHD_Response *reply;
     347              :   MHD_RESULT ret;
     348              : 
     349            0 :   res = TALER_TEMPLATING_build (connection,
     350              :                                 &http_status,
     351              :                                 template,
     352              :                                 instance_id,
     353              :                                 taler_uri,
     354              :                                 root,
     355              :                                 &reply);
     356            0 :   if (GNUNET_SYSERR == res)
     357            0 :     return res;
     358            0 :   GNUNET_break (MHD_NO !=
     359              :                 MHD_add_response_header (reply,
     360              :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     361              :                                          "text/html"));
     362            0 :   ret = MHD_queue_response (connection,
     363              :                             http_status,
     364              :                             reply);
     365            0 :   MHD_destroy_response (reply);
     366            0 :   if (MHD_NO == ret)
     367            0 :     return GNUNET_SYSERR;
     368              :   return (res == GNUNET_OK)
     369              :     ? GNUNET_OK
     370            0 :     : GNUNET_NO;
     371              : }
     372              : 
     373              : 
     374              : /**
     375              :  * Function called with a template's filename.
     376              :  *
     377              :  * @param cls closure, NULL
     378              :  * @param filename complete filename (absolute path)
     379              :  * @return #GNUNET_OK to continue to iterate,
     380              :  *  #GNUNET_NO to stop iteration with no error,
     381              :  *  #GNUNET_SYSERR to abort iteration with error!
     382              :  */
     383              : static enum GNUNET_GenericReturnValue
     384          360 : load_template (void *cls,
     385              :                const char *filename)
     386              : {
     387              :   char *lang;
     388              :   char *end;
     389              :   int fd;
     390              :   struct stat sb;
     391              :   char *map;
     392              :   const char *name;
     393              : 
     394              :   (void) cls;
     395          360 :   if ('.' == filename[0])
     396            0 :     return GNUNET_OK;
     397          360 :   name = strrchr (filename,
     398              :                   '/');
     399          360 :   if (NULL == name)
     400            0 :     name = filename;
     401              :   else
     402          360 :     name++;
     403          360 :   lang = strchr (name,
     404              :                  '.');
     405          360 :   if (NULL == lang)
     406            0 :     return GNUNET_OK; /* name must include .$LANG */
     407          360 :   lang++;
     408          360 :   end = strchr (lang,
     409              :                 '.');
     410          360 :   if ( (NULL == end) ||
     411          360 :        (0 != strcmp (end,
     412              :                      ".must")) )
     413            0 :     return GNUNET_OK; /* name must end with '.must' */
     414              : 
     415              :   /* finally open template */
     416          360 :   fd = open (filename,
     417              :              O_RDONLY);
     418          360 :   if (-1 == fd)
     419              :   {
     420            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     421              :                               "open",
     422              :                               filename);
     423              : 
     424            0 :     return GNUNET_SYSERR;
     425              :   }
     426          360 :   if (0 !=
     427          360 :       fstat (fd,
     428              :              &sb))
     429              :   {
     430            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     431              :                               "fstat",
     432              :                               filename);
     433            0 :     GNUNET_break (0 == close (fd));
     434            0 :     return GNUNET_OK;
     435              :   }
     436          360 :   map = GNUNET_malloc_large (sb.st_size + 1);
     437          360 :   if (NULL == map)
     438              :   {
     439            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     440              :                          "malloc");
     441            0 :     GNUNET_break (0 == close (fd));
     442            0 :     return GNUNET_SYSERR;
     443              :   }
     444          360 :   if (sb.st_size !=
     445          360 :       read (fd,
     446              :             map,
     447          360 :             sb.st_size))
     448              :   {
     449            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     450              :                               "read",
     451              :                               filename);
     452            0 :     GNUNET_break (0 == close (fd));
     453            0 :     return GNUNET_OK;
     454              :   }
     455          360 :   GNUNET_break (0 == close (fd));
     456          360 :   GNUNET_array_grow (loaded,
     457              :                      loaded_length,
     458              :                      loaded_length + 1);
     459          360 :   loaded[loaded_length - 1].name = GNUNET_strndup (name,
     460              :                                                    (lang - 1) - name);
     461          360 :   loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
     462              :                                                    end - lang);
     463          360 :   loaded[loaded_length - 1].value = map;
     464          360 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     465              :               "Loading template `%s' (%s)\n",
     466              :               filename,
     467              :               loaded[loaded_length - 1].name);
     468          360 :   return GNUNET_OK;
     469              : }
     470              : 
     471              : 
     472              : MHD_RESULT
     473            0 : TALER_TEMPLATING_reply_error (
     474              :   struct MHD_Connection *connection,
     475              :   const char *template_basename,
     476              :   unsigned int http_status,
     477              :   enum TALER_ErrorCode ec,
     478              :   const char *detail)
     479              : {
     480              :   json_t *data;
     481              :   enum GNUNET_GenericReturnValue ret;
     482              : 
     483            0 :   data = GNUNET_JSON_PACK (
     484              :     GNUNET_JSON_pack_uint64 ("ec",
     485              :                              ec),
     486              :     GNUNET_JSON_pack_string ("hint",
     487              :                              TALER_ErrorCode_get_hint (ec)),
     488              :     GNUNET_JSON_pack_allow_null (
     489              :       GNUNET_JSON_pack_string ("detail",
     490              :                                detail))
     491              :     );
     492            0 :   ret = TALER_TEMPLATING_reply (connection,
     493              :                                 http_status,
     494              :                                 template_basename,
     495              :                                 NULL,
     496              :                                 NULL,
     497              :                                 data);
     498            0 :   json_decref (data);
     499            0 :   switch (ret)
     500              :   {
     501            0 :   case GNUNET_OK:
     502            0 :     return MHD_YES;
     503            0 :   case GNUNET_NO:
     504            0 :     return MHD_YES;
     505            0 :   case GNUNET_SYSERR:
     506            0 :     return MHD_NO;
     507              :   }
     508            0 :   GNUNET_assert (0);
     509              :   return MHD_NO;
     510              : }
     511              : 
     512              : 
     513              : enum GNUNET_GenericReturnValue
     514           18 : TALER_TEMPLATING_init (const struct GNUNET_OS_ProjectData *pd)
     515              : {
     516              :   char *dn;
     517              :   int ret;
     518              : 
     519              :   {
     520              :     char *path;
     521              : 
     522           18 :     path = GNUNET_OS_installation_get_path (pd,
     523              :                                             GNUNET_OS_IPK_DATADIR);
     524           18 :     if (NULL == path)
     525              :     {
     526            0 :       GNUNET_break (0);
     527            0 :       return GNUNET_SYSERR;
     528              :     }
     529           18 :     GNUNET_asprintf (&dn,
     530              :                      "%s/templates/",
     531              :                      path);
     532           18 :     GNUNET_free (path);
     533              :   }
     534           18 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     535              :               "Loading templates from `%s'\n",
     536              :               dn);
     537           18 :   ret = GNUNET_DISK_directory_scan (dn,
     538              :                                     &load_template,
     539              :                                     NULL);
     540           18 :   GNUNET_free (dn);
     541           18 :   if (-1 == ret)
     542              :   {
     543            0 :     GNUNET_break (0);
     544            0 :     return GNUNET_SYSERR;
     545              :   }
     546           18 :   return GNUNET_OK;
     547              : }
     548              : 
     549              : 
     550              : void
     551           18 : TALER_TEMPLATING_done (void)
     552              : {
     553          378 :   for (unsigned int i = 0; i<loaded_length; i++)
     554              :   {
     555          360 :     GNUNET_free (loaded[i].name);
     556          360 :     GNUNET_free (loaded[i].lang);
     557          360 :     GNUNET_free (loaded[i].value);
     558              :   }
     559           18 :   GNUNET_array_grow (loaded,
     560              :                      loaded_length,
     561              :                      0);
     562           18 : }
     563              : 
     564              : 
     565              : /* end of templating_api.c */
        

Generated by: LCOV version 2.0-1