LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_templating.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 90 141 63.8 %
Date: 2021-08-30 06:54:17 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14