LCOV - code coverage report
Current view: top level - templating - templating_api.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 44.5 % 182 81
Test Date: 2026-05-12 15:34:29 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 :   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];
      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;
     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              :   struct TVE *tve;
     246              : 
     247              :   {
     248              :     const char *tmpl;
     249              :     int eno;
     250              : 
     251            3 :     tve = lookup_template (connection,
     252              :                            template);
     253            3 :     if (NULL == tve)
     254              :     {
     255            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     256              :                   "Failed to load template `%s'\n",
     257              :                   template);
     258            0 :       *http_status = MHD_HTTP_NOT_ACCEPTABLE;
     259            0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
     260              :                                      template);
     261            0 :       return GNUNET_NO;
     262              :     }
     263            3 :     tmpl = tve->value;
     264              :     /* Add default values to the context */
     265            3 :     if (NULL != instance_id)
     266              :     {
     267            0 :       char *static_url = make_static_url (connection,
     268              :                                           instance_id);
     269              : 
     270            0 :       GNUNET_break (0 ==
     271              :                     json_object_set_new ((json_t *) root,
     272              :                                          "static_url",
     273              :                                          json_string (static_url)));
     274            0 :       GNUNET_free (static_url);
     275              :     }
     276            3 :     if (0 !=
     277            3 :         (eno = mustach_jansson_mem (tmpl,
     278              :                                     0,
     279              :                                     (json_t *) root,
     280              :                                     Mustach_With_NoExtensions,
     281              :                                     &body,
     282              :                                     &body_size)))
     283              :     {
     284            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     285              :                   "mustach failed on template `%s' with error %d\n",
     286              :                   template,
     287              :                   eno);
     288            0 :       *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     289            0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
     290              :                                      template);
     291            0 :       return GNUNET_NO;
     292              :     }
     293              :   }
     294              : 
     295              :   /* try to compress reply if client allows it */
     296              :   {
     297            3 :     bool compressed = false;
     298              : 
     299            3 :     if (TALER_MHD_CT_DEFLATE ==
     300            3 :         TALER_MHD_can_compress (connection,
     301              :                                 TALER_MHD_CT_DEFLATE))
     302              :     {
     303            3 :       compressed = TALER_MHD_body_compress ((void **) &body,
     304              :                                             &body_size);
     305              :     }
     306            3 :     *reply = MHD_create_response_from_buffer (body_size,
     307              :                                               body,
     308              :                                               MHD_RESPMEM_MUST_FREE);
     309            3 :     if (NULL == *reply)
     310              :     {
     311            0 :       GNUNET_break (0);
     312            0 :       return GNUNET_SYSERR;
     313              :     }
     314            3 :     if (compressed)
     315              :     {
     316            3 :       if (MHD_NO ==
     317            3 :           MHD_add_response_header (*reply,
     318              :                                    MHD_HTTP_HEADER_CONTENT_ENCODING,
     319              :                                    "deflate"))
     320              :       {
     321            0 :         GNUNET_break (0);
     322            0 :         MHD_destroy_response (*reply);
     323            0 :         *reply = NULL;
     324            0 :         return GNUNET_SYSERR;
     325              :       }
     326              :     }
     327              :   }
     328            3 :   if (NULL != tve->lang)
     329            3 :     GNUNET_break (MHD_YES ==
     330              :                   MHD_add_response_header (*reply,
     331              :                                            MHD_HTTP_HEADER_CONTENT_LANGUAGE,
     332              :                                            tve->lang));
     333              : 
     334              :   /* Add standard headers */
     335            3 :   if (NULL != taler_uri)
     336            0 :     GNUNET_break (MHD_NO !=
     337              :                   MHD_add_response_header (*reply,
     338              :                                            "Taler",
     339              :                                            taler_uri));
     340            3 :   return GNUNET_OK;
     341              : }
     342              : 
     343              : 
     344              : enum GNUNET_GenericReturnValue
     345            0 : TALER_TEMPLATING_reply (struct MHD_Connection *connection,
     346              :                         unsigned int http_status,
     347              :                         const char *template,
     348              :                         const char *instance_id,
     349              :                         const char *taler_uri,
     350              :                         const json_t *root)
     351              : {
     352              :   enum GNUNET_GenericReturnValue res;
     353              :   struct MHD_Response *reply;
     354              :   enum MHD_Result ret;
     355              : 
     356            0 :   res = TALER_TEMPLATING_build (connection,
     357              :                                 &http_status,
     358              :                                 template,
     359              :                                 instance_id,
     360              :                                 taler_uri,
     361              :                                 root,
     362              :                                 &reply);
     363            0 :   if (GNUNET_SYSERR == res)
     364            0 :     return res;
     365            0 :   GNUNET_break (MHD_NO !=
     366              :                 MHD_add_response_header (reply,
     367              :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     368              :                                          "text/html"));
     369              :   // FIXME: set Vary header!
     370            0 :   ret = MHD_queue_response (connection,
     371              :                             http_status,
     372              :                             reply);
     373            0 :   MHD_destroy_response (reply);
     374            0 :   if (MHD_NO == ret)
     375            0 :     return GNUNET_SYSERR;
     376              :   return (res == GNUNET_OK)
     377              :     ? GNUNET_OK
     378            0 :     : GNUNET_NO;
     379              : }
     380              : 
     381              : 
     382              : /**
     383              :  * Function called with a template's filename.
     384              :  *
     385              :  * @param cls closure, NULL
     386              :  * @param filename complete filename (absolute path)
     387              :  * @return #GNUNET_OK to continue to iterate,
     388              :  *  #GNUNET_NO to stop iteration with no error,
     389              :  *  #GNUNET_SYSERR to abort iteration with error!
     390              :  */
     391              : static enum GNUNET_GenericReturnValue
     392          360 : load_template (void *cls,
     393              :                const char *filename)
     394              : {
     395              :   char *lang;
     396              :   char *end;
     397              :   int fd;
     398              :   struct stat sb;
     399              :   char *map;
     400              :   const char *name;
     401              : 
     402              :   (void) cls;
     403          360 :   if ('.' == filename[0])
     404            0 :     return GNUNET_OK;
     405          360 :   name = strrchr (filename,
     406              :                   '/');
     407          360 :   if (NULL == name)
     408            0 :     name = filename;
     409              :   else
     410          360 :     name++;
     411          360 :   lang = strchr (name,
     412              :                  '.');
     413          360 :   if (NULL == lang)
     414            0 :     return GNUNET_OK; /* name must include .$LANG */
     415          360 :   lang++;
     416          360 :   end = strchr (lang,
     417              :                 '.');
     418          360 :   if ( (NULL == end) ||
     419          360 :        (0 != strcmp (end,
     420              :                      ".must")) )
     421            0 :     return GNUNET_OK; /* name must end with '.must' */
     422              : 
     423              :   /* finally open template */
     424          360 :   fd = open (filename,
     425              :              O_RDONLY);
     426          360 :   if (-1 == fd)
     427              :   {
     428            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     429              :                               "open",
     430              :                               filename);
     431              : 
     432            0 :     return GNUNET_SYSERR;
     433              :   }
     434          360 :   if (0 !=
     435          360 :       fstat (fd,
     436              :              &sb))
     437              :   {
     438            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     439              :                               "fstat",
     440              :                               filename);
     441            0 :     GNUNET_break (0 == close (fd));
     442            0 :     return GNUNET_OK;
     443              :   }
     444          360 :   map = GNUNET_malloc_large (sb.st_size + 1);
     445          360 :   if (NULL == map)
     446              :   {
     447            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     448              :                          "malloc");
     449            0 :     GNUNET_break (0 == close (fd));
     450            0 :     return GNUNET_SYSERR;
     451              :   }
     452          360 :   if (sb.st_size !=
     453          360 :       read (fd,
     454              :             map,
     455          360 :             sb.st_size))
     456              :   {
     457            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     458              :                               "read",
     459              :                               filename);
     460            0 :     GNUNET_break (0 == close (fd));
     461            0 :     return GNUNET_OK;
     462              :   }
     463          360 :   GNUNET_break (0 == close (fd));
     464          360 :   GNUNET_array_grow (loaded,
     465              :                      loaded_length,
     466              :                      loaded_length + 1);
     467          360 :   loaded[loaded_length - 1].name = GNUNET_strndup (name,
     468              :                                                    (lang - 1) - name);
     469          360 :   loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
     470              :                                                    end - lang);
     471          360 :   loaded[loaded_length - 1].value = map;
     472          360 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     473              :               "Loading template `%s' (%s)\n",
     474              :               filename,
     475              :               loaded[loaded_length - 1].name);
     476          360 :   return GNUNET_OK;
     477              : }
     478              : 
     479              : 
     480              : enum MHD_Result
     481            0 : TALER_TEMPLATING_reply_error (
     482              :   struct MHD_Connection *connection,
     483              :   const char *template_basename,
     484              :   unsigned int http_status,
     485              :   enum TALER_ErrorCode ec,
     486              :   const char *detail)
     487              : {
     488              :   json_t *data;
     489              :   enum GNUNET_GenericReturnValue ret;
     490              : 
     491            0 :   data = GNUNET_JSON_PACK (
     492              :     GNUNET_JSON_pack_uint64 ("ec",
     493              :                              ec),
     494              :     GNUNET_JSON_pack_string ("hint",
     495              :                              TALER_ErrorCode_get_hint (ec)),
     496              :     GNUNET_JSON_pack_allow_null (
     497              :       GNUNET_JSON_pack_string ("detail",
     498              :                                detail))
     499              :     );
     500            0 :   ret = TALER_TEMPLATING_reply (connection,
     501              :                                 http_status,
     502              :                                 template_basename,
     503              :                                 NULL,
     504              :                                 NULL,
     505              :                                 data);
     506            0 :   json_decref (data);
     507            0 :   switch (ret)
     508              :   {
     509            0 :   case GNUNET_OK:
     510            0 :     return MHD_YES;
     511            0 :   case GNUNET_NO:
     512            0 :     return MHD_YES;
     513            0 :   case GNUNET_SYSERR:
     514            0 :     return MHD_NO;
     515              :   }
     516            0 :   GNUNET_assert (0);
     517              :   return MHD_NO;
     518              : }
     519              : 
     520              : 
     521              : enum GNUNET_GenericReturnValue
     522           18 : TALER_TEMPLATING_init (const struct GNUNET_OS_ProjectData *pd)
     523              : {
     524              :   char *dn;
     525              :   int ret;
     526              : 
     527              :   {
     528              :     char *path;
     529              : 
     530           18 :     path = GNUNET_OS_installation_get_path (pd,
     531              :                                             GNUNET_OS_IPK_DATADIR);
     532           18 :     if (NULL == path)
     533              :     {
     534            0 :       GNUNET_break (0);
     535            0 :       return GNUNET_SYSERR;
     536              :     }
     537           18 :     GNUNET_asprintf (&dn,
     538              :                      "%s/templates/",
     539              :                      path);
     540           18 :     GNUNET_free (path);
     541              :   }
     542           18 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     543              :               "Loading templates from `%s'\n",
     544              :               dn);
     545           18 :   ret = GNUNET_DISK_directory_scan (dn,
     546              :                                     &load_template,
     547              :                                     NULL);
     548           18 :   GNUNET_free (dn);
     549           18 :   if (-1 == ret)
     550              :   {
     551            0 :     GNUNET_break (0);
     552            0 :     return GNUNET_SYSERR;
     553              :   }
     554           18 :   return GNUNET_OK;
     555              : }
     556              : 
     557              : 
     558              : void
     559           18 : TALER_TEMPLATING_done (void)
     560              : {
     561          378 :   for (unsigned int i = 0; i<loaded_length; i++)
     562              :   {
     563          360 :     GNUNET_free (loaded[i].name);
     564          360 :     GNUNET_free (loaded[i].lang);
     565          360 :     GNUNET_free (loaded[i].value);
     566              :   }
     567           18 :   GNUNET_array_grow (loaded,
     568              :                      loaded_length,
     569              :                      0);
     570           18 : }
     571              : 
     572              : 
     573              : /* end of templating_api.c */
        

Generated by: LCOV version 2.0-1