LCOV - code coverage report
Current view: top level - templating - templating_api.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 0 146 0.0 %
Date: 2022-08-25 06:15:09 Functions: 0 7 0.0 %
Legend: Lines: hit not hit

          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"
      22             : #include <gnunet/gnunet_util_lib.h>
      23             : #include "taler_util.h"
      24             : #include "taler_mhd_lib.h"
      25             : #include "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           0 : lookup_template (struct MHD_Connection *connection,
      77             :                  const char *name)
      78             : {
      79           0 :   struct TVE *best = NULL;
      80             :   const char *lang;
      81             : 
      82           0 :   lang = MHD_lookup_connection_value (connection,
      83             :                                       MHD_HEADER_KIND,
      84             :                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
      85           0 :   if (NULL == lang)
      86           0 :     lang = "en";
      87             :   /* find best match by language */
      88           0 :   for (unsigned int i = 0; i<loaded_length; i++)
      89             :   {
      90           0 :     if (0 != strcmp (loaded[i].name,
      91             :                      name))
      92           0 :       continue; /* does not match by name */
      93           0 :     if ( (NULL == best) ||
      94           0 :          (TALER_language_matches (lang,
      95           0 :                                   loaded[i].lang) >
      96           0 :           TALER_language_matches (lang,
      97           0 :                                   best->lang) ) )
      98           0 :       best = &loaded[i];
      99             :   }
     100           0 :   if (NULL == best)
     101             :   {
     102           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     103             :                 "No templates found in `%s'\n",
     104             :                 name);
     105           0 :     return NULL;
     106             :   }
     107           0 :   return best->value;
     108             : }
     109             : 
     110             : 
     111             : /**
     112             :  * Get the base URL for static resources.
     113             :  *
     114             :  * @param con the MHD connection
     115             :  * @param instance_id the instance ID
     116             :  * @returns the static files base URL, guaranteed
     117             :  *          to have a trailing slash.
     118             :  */
     119             : static char *
     120           0 : make_static_url (struct MHD_Connection *con,
     121             :                  const char *instance_id)
     122             : {
     123             :   const char *host;
     124             :   const char *forwarded_host;
     125             :   const char *uri_path;
     126           0 :   struct GNUNET_Buffer buf = { 0 };
     127             : 
     128           0 :   host = MHD_lookup_connection_value (con,
     129             :                                       MHD_HEADER_KIND,
     130             :                                       "Host");
     131           0 :   forwarded_host = MHD_lookup_connection_value (con,
     132             :                                                 MHD_HEADER_KIND,
     133             :                                                 "X-Forwarded-Host");
     134             : 
     135           0 :   uri_path = MHD_lookup_connection_value (con,
     136             :                                           MHD_HEADER_KIND,
     137             :                                           "X-Forwarded-Prefix");
     138           0 :   if (NULL != forwarded_host)
     139           0 :     host = forwarded_host;
     140             : 
     141           0 :   if (NULL == host)
     142             :   {
     143           0 :     GNUNET_break (0);
     144           0 :     return NULL;
     145             :   }
     146             : 
     147           0 :   GNUNET_assert (NULL != instance_id);
     148             : 
     149           0 :   if (GNUNET_NO == TALER_mhd_is_https (con))
     150           0 :     GNUNET_buffer_write_str (&buf,
     151             :                              "http://");
     152             :   else
     153           0 :     GNUNET_buffer_write_str (&buf,
     154             :                              "https://");
     155           0 :   GNUNET_buffer_write_str (&buf,
     156             :                            host);
     157           0 :   if (NULL != uri_path)
     158           0 :     GNUNET_buffer_write_path (&buf,
     159             :                               uri_path);
     160           0 :   if (0 != strcmp ("default",
     161             :                    instance_id))
     162             :   {
     163           0 :     GNUNET_buffer_write_path (&buf,
     164             :                               "instances");
     165           0 :     GNUNET_buffer_write_path (&buf,
     166             :                               instance_id);
     167             :   }
     168           0 :   GNUNET_buffer_write_path (&buf,
     169             :                             "static/");
     170           0 :   return GNUNET_buffer_reap_str (&buf);
     171             : }
     172             : 
     173             : 
     174             : enum GNUNET_GenericReturnValue
     175           0 : TALER_TEMPLATING_build (struct MHD_Connection *connection,
     176             :                         unsigned int *http_status,
     177             :                         const char *template,
     178             :                         const char *instance_id,
     179             :                         const char *taler_uri,
     180             :                         const json_t *root,
     181             :                         struct MHD_Response **reply)
     182             : {
     183             :   char *body;
     184             :   size_t body_size;
     185             : 
     186             :   {
     187             :     const char *tmpl;
     188             :     int eno;
     189             : 
     190           0 :     tmpl = lookup_template (connection,
     191             :                             template);
     192           0 :     if (NULL == tmpl)
     193             :     {
     194             :       /* FIXME: should this not be an
     195             :          internal failure? The language
     196             :          mismatch is not critical here! */
     197           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     198             :                   "Failed to load template `%s'\n",
     199             :                   template);
     200           0 :       *http_status = MHD_HTTP_NOT_ACCEPTABLE;
     201           0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
     202             :                                      template);
     203           0 :       return GNUNET_NO;
     204             :     }
     205             :     /* Add default values to the context */
     206           0 :     if (NULL != instance_id)
     207             :     {
     208           0 :       char *static_url = make_static_url (connection,
     209             :                                           instance_id);
     210             : 
     211           0 :       GNUNET_break (0 ==
     212             :                     json_object_set_new ((json_t *) root,
     213             :                                          "static_url",
     214             :                                          json_string (static_url)));
     215           0 :       GNUNET_free (static_url);
     216             :     }
     217           0 :     if (0 !=
     218           0 :         (eno = mustach_jansson (tmpl,
     219             :                                 (json_t *) root,
     220             :                                 &body,
     221             :                                 &body_size)))
     222             :     {
     223           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     224             :                   "mustach failed on template `%s' with error %d\n",
     225             :                   template,
     226             :                   eno);
     227           0 :       *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     228           0 :       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
     229             :                                      template);
     230           0 :       return GNUNET_NO;
     231             :     }
     232             :   }
     233             : 
     234             :   /* try to compress reply if client allows it */
     235             :   {
     236           0 :     bool compressed = false;
     237             : 
     238           0 :     if (MHD_YES ==
     239           0 :         TALER_MHD_can_compress (connection))
     240             :     {
     241           0 :       compressed = TALER_MHD_body_compress ((void **) &body,
     242             :                                             &body_size);
     243             :     }
     244           0 :     *reply = MHD_create_response_from_buffer (body_size,
     245             :                                               body,
     246             :                                               MHD_RESPMEM_MUST_FREE);
     247           0 :     if (NULL == *reply)
     248             :     {
     249           0 :       GNUNET_break (0);
     250           0 :       return GNUNET_SYSERR;
     251             :     }
     252           0 :     if (compressed)
     253             :     {
     254           0 :       if (MHD_NO ==
     255           0 :           MHD_add_response_header (*reply,
     256             :                                    MHD_HTTP_HEADER_CONTENT_ENCODING,
     257             :                                    "deflate"))
     258             :       {
     259           0 :         GNUNET_break (0);
     260           0 :         MHD_destroy_response (*reply);
     261           0 :         *reply = NULL;
     262           0 :         return GNUNET_SYSERR;
     263             :       }
     264             :     }
     265             :   }
     266             : 
     267             :   /* Add standard headers */
     268           0 :   if (NULL != taler_uri)
     269           0 :     GNUNET_break (MHD_NO !=
     270             :                   MHD_add_response_header (*reply,
     271             :                                            "Taler",
     272             :                                            taler_uri));
     273           0 :   GNUNET_break (MHD_NO !=
     274             :                 MHD_add_response_header (*reply,
     275             :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     276             :                                          "text/html"));
     277           0 :   return GNUNET_OK;
     278             : }
     279             : 
     280             : 
     281             : enum GNUNET_GenericReturnValue
     282           0 : TALER_TEMPLATING_reply (struct MHD_Connection *connection,
     283             :                         unsigned int http_status,
     284             :                         const char *template,
     285             :                         const char *instance_id,
     286             :                         const char *taler_uri,
     287             :                         const json_t *root)
     288             : {
     289             :   enum GNUNET_GenericReturnValue res;
     290             :   struct MHD_Response *reply;
     291             :   MHD_RESULT ret;
     292             : 
     293           0 :   res = TALER_TEMPLATING_build (connection,
     294             :                                 &http_status,
     295             :                                 template,
     296             :                                 instance_id,
     297             :                                 taler_uri,
     298             :                                 root,
     299             :                                 &reply);
     300           0 :   if (GNUNET_SYSERR == res)
     301           0 :     return res;
     302           0 :   ret = MHD_queue_response (connection,
     303             :                             http_status,
     304             :                             reply);
     305           0 :   MHD_destroy_response (reply);
     306           0 :   if (MHD_NO == ret)
     307           0 :     return GNUNET_SYSERR;
     308             :   return (res == GNUNET_OK)
     309             :     ? GNUNET_OK
     310           0 :     : GNUNET_NO;
     311             : }
     312             : 
     313             : 
     314             : /**
     315             :  * Function called with a template's filename.
     316             :  *
     317             :  * @param cls closure, NULL
     318             :  * @param filename complete filename (absolute path)
     319             :  * @return #GNUNET_OK to continue to iterate,
     320             :  *  #GNUNET_NO to stop iteration with no error,
     321             :  *  #GNUNET_SYSERR to abort iteration with error!
     322             :  */
     323             : static enum GNUNET_GenericReturnValue
     324           0 : load_template (void *cls,
     325             :                const char *filename)
     326             : {
     327             :   char *lang;
     328             :   char *end;
     329             :   int fd;
     330             :   struct stat sb;
     331             :   char *map;
     332             :   const char *name;
     333             : 
     334             :   (void) cls;
     335           0 :   if ('.' == filename[0])
     336           0 :     return GNUNET_OK;
     337             : 
     338           0 :   name = strrchr (filename,
     339             :                   '/');
     340           0 :   if (NULL == name)
     341           0 :     name = filename;
     342             :   else
     343           0 :     name++;
     344           0 :   lang = strchr (name,
     345             :                  '.');
     346           0 :   if (NULL == lang)
     347           0 :     return GNUNET_OK; /* name must include .$LANG */
     348           0 :   lang++;
     349           0 :   end = strchr (lang,
     350             :                 '.');
     351           0 :   if ( (NULL == end) ||
     352           0 :        (0 != strcmp (end,
     353             :                      ".must")) )
     354           0 :     return GNUNET_OK; /* name must end with '.must' */
     355             : 
     356             :   /* finally open template */
     357           0 :   fd = open (filename,
     358             :              O_RDONLY);
     359           0 :   if (-1 == fd)
     360             :   {
     361           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     362             :                               "open",
     363             :                               filename);
     364             : 
     365           0 :     return GNUNET_SYSERR;
     366             :   }
     367           0 :   if (0 !=
     368           0 :       fstat (fd,
     369             :              &sb))
     370             :   {
     371           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     372             :                               "open",
     373             :                               filename);
     374           0 :     GNUNET_break (0 == close (fd));
     375           0 :     return GNUNET_OK;
     376             :   }
     377           0 :   map = GNUNET_malloc_large (sb.st_size + 1);
     378           0 :   if (NULL == map)
     379             :   {
     380           0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     381             :                          "malloc");
     382           0 :     GNUNET_break (0 == close (fd));
     383           0 :     return GNUNET_SYSERR;
     384             :   }
     385           0 :   if (sb.st_size !=
     386           0 :       read (fd,
     387             :             map,
     388           0 :             sb.st_size))
     389             :   {
     390           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     391             :                               "read",
     392             :                               filename);
     393           0 :     GNUNET_break (0 == close (fd));
     394           0 :     return GNUNET_OK;
     395             :   }
     396           0 :   GNUNET_break (0 == close (fd));
     397           0 :   GNUNET_array_grow (loaded,
     398             :                      loaded_length,
     399             :                      loaded_length + 1);
     400           0 :   loaded[loaded_length - 1].name = GNUNET_strndup (name,
     401             :                                                    (lang - 1) - name);
     402           0 :   loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
     403             :                                                    end - lang);
     404           0 :   loaded[loaded_length - 1].value = map;
     405           0 :   return GNUNET_OK;
     406             : }
     407             : 
     408             : 
     409             : enum GNUNET_GenericReturnValue
     410           0 : TALER_TEMPLATING_init (const char *subsystem)
     411             : {
     412             :   char *dn;
     413             :   int ret;
     414             : 
     415             :   {
     416             :     char *path;
     417             : 
     418           0 :     path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
     419           0 :     if (NULL == path)
     420             :     {
     421           0 :       GNUNET_break (0);
     422           0 :       return GNUNET_SYSERR;
     423             :     }
     424           0 :     GNUNET_asprintf (&dn,
     425             :                      "%s/%s/templates/",
     426             :                      path,
     427             :                      subsystem);
     428           0 :     GNUNET_free (path);
     429             :   }
     430           0 :   ret = GNUNET_DISK_directory_scan (dn,
     431             :                                     &load_template,
     432             :                                     NULL);
     433           0 :   GNUNET_free (dn);
     434           0 :   if (-1 == ret)
     435             :   {
     436           0 :     GNUNET_break (0);
     437           0 :     return GNUNET_SYSERR;
     438             :   }
     439           0 :   return GNUNET_OK;
     440             : }
     441             : 
     442             : 
     443             : void
     444           0 : TALER_TEMPLATING_done (void)
     445             : {
     446           0 :   for (unsigned int i = 0; i<loaded_length; i++)
     447             :   {
     448           0 :     GNUNET_free (loaded[i].name);
     449           0 :     GNUNET_free (loaded[i].lang);
     450           0 :     GNUNET_free (loaded[i].value);
     451             :   }
     452           0 :   GNUNET_array_grow (loaded,
     453             :                      loaded_length,
     454             :                      0);
     455           0 : }
     456             : 
     457             : 
     458             : /* end of templating_api.c */

Generated by: LCOV version 1.14