exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

templating_api.c (15446B)


      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"
     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 lookup_template (struct MHD_Connection *connection,
     77                  const char *name)
     78 {
     79   struct TVE *best = NULL;
     80   double best_q = 0.0;
     81   const char *lang;
     82 
     83   lang = MHD_lookup_connection_value (connection,
     84                                       MHD_HEADER_KIND,
     85                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
     86   if (NULL == lang)
     87     lang = "en";
     88   /* find best match by language */
     89   for (unsigned int i = 0; i<loaded_length; i++)
     90   {
     91     double q;
     92 
     93     if (0 != strcmp (loaded[i].name,
     94                      name))
     95       continue; /* does not match by name */
     96     if (NULL == loaded[i].lang) /* no language == always best match */
     97       return loaded[i].value;
     98     q = TALER_pattern_matches (lang,
     99                                loaded[i].lang);
    100     if (q < best_q)
    101       continue;
    102     best_q = q;
    103     best = &loaded[i];
    104   }
    105   if (NULL == best)
    106   {
    107     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    108                 "No templates found for `%s'\n",
    109                 name);
    110     return NULL;
    111   }
    112   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 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   struct GNUNET_Buffer buf = { 0 };
    132 
    133   host = MHD_lookup_connection_value (con,
    134                                       MHD_HEADER_KIND,
    135                                       "Host");
    136   forwarded_host = MHD_lookup_connection_value (con,
    137                                                 MHD_HEADER_KIND,
    138                                                 "X-Forwarded-Host");
    139 
    140   uri_path = MHD_lookup_connection_value (con,
    141                                           MHD_HEADER_KIND,
    142                                           "X-Forwarded-Prefix");
    143   if (NULL != forwarded_host)
    144     host = forwarded_host;
    145 
    146   if (NULL == host)
    147   {
    148     GNUNET_break (0);
    149     return NULL;
    150   }
    151 
    152   GNUNET_assert (NULL != instance_id);
    153 
    154   if (GNUNET_NO == TALER_mhd_is_https (con))
    155     GNUNET_buffer_write_str (&buf,
    156                              "http://");
    157   else
    158     GNUNET_buffer_write_str (&buf,
    159                              "https://");
    160   GNUNET_buffer_write_str (&buf,
    161                            host);
    162   if (NULL != uri_path)
    163     GNUNET_buffer_write_path (&buf,
    164                               uri_path);
    165   if (0 != strcmp ("default",
    166                    instance_id))
    167   {
    168     GNUNET_buffer_write_path (&buf,
    169                               "instances");
    170     GNUNET_buffer_write_path (&buf,
    171                               instance_id);
    172   }
    173   GNUNET_buffer_write_path (&buf,
    174                             "static/");
    175   return GNUNET_buffer_reap_str (&buf);
    176 }
    177 
    178 
    179 int
    180 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   if (0 !=
    188       (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     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    196                 "mustach failed on template with error %d\n",
    197                 eno);
    198     *result = NULL;
    199     *result_size = 0;
    200     return eno;
    201   }
    202   return eno;
    203 }
    204 
    205 
    206 int
    207 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   if (0 !=
    216       (eno = mustach_jansson_mem (tmpl,
    217                                   tmpl_len,
    218                                   (json_t *) root,
    219                                   Mustach_With_AllExtensions,
    220                                   (char **) result,
    221                                   result_size)))
    222   {
    223     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    224                 "mustach failed on template with error %d\n",
    225                 eno);
    226     *result = NULL;
    227     *result_size = 0;
    228     return eno;
    229   }
    230   return eno;
    231 }
    232 
    233 
    234 enum GNUNET_GenericReturnValue
    235 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     tmpl = lookup_template (connection,
    251                             template);
    252     if (NULL == tmpl)
    253     {
    254       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    255                   "Failed to load template `%s'\n",
    256                   template);
    257       *http_status = MHD_HTTP_NOT_ACCEPTABLE;
    258       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
    259                                      template);
    260       return GNUNET_NO;
    261     }
    262     /* Add default values to the context */
    263     if (NULL != instance_id)
    264     {
    265       char *static_url = make_static_url (connection,
    266                                           instance_id);
    267 
    268       GNUNET_break (0 ==
    269                     json_object_set_new ((json_t *) root,
    270                                          "static_url",
    271                                          json_string (static_url)));
    272       GNUNET_free (static_url);
    273     }
    274     if (0 !=
    275         (eno = mustach_jansson_mem (tmpl,
    276                                     0,
    277                                     (json_t *) root,
    278                                     Mustach_With_NoExtensions,
    279                                     &body,
    280                                     &body_size)))
    281     {
    282       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    283                   "mustach failed on template `%s' with error %d\n",
    284                   template,
    285                   eno);
    286       *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    287       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
    288                                      template);
    289       return GNUNET_NO;
    290     }
    291   }
    292 
    293   /* try to compress reply if client allows it */
    294   {
    295     bool compressed = false;
    296 
    297     if (TALER_MHD_CT_DEFLATE ==
    298         TALER_MHD_can_compress (connection,
    299                                 TALER_MHD_CT_DEFLATE))
    300     {
    301       compressed = TALER_MHD_body_compress ((void **) &body,
    302                                             &body_size);
    303     }
    304     *reply = MHD_create_response_from_buffer (body_size,
    305                                               body,
    306                                               MHD_RESPMEM_MUST_FREE);
    307     if (NULL == *reply)
    308     {
    309       GNUNET_break (0);
    310       return GNUNET_SYSERR;
    311     }
    312     if (compressed)
    313     {
    314       if (MHD_NO ==
    315           MHD_add_response_header (*reply,
    316                                    MHD_HTTP_HEADER_CONTENT_ENCODING,
    317                                    "deflate"))
    318       {
    319         GNUNET_break (0);
    320         MHD_destroy_response (*reply);
    321         *reply = NULL;
    322         return GNUNET_SYSERR;
    323       }
    324     }
    325   }
    326 
    327   /* Add standard headers */
    328   if (NULL != taler_uri)
    329     GNUNET_break (MHD_NO !=
    330                   MHD_add_response_header (*reply,
    331                                            "Taler",
    332                                            taler_uri));
    333   GNUNET_break (MHD_NO !=
    334                 MHD_add_response_header (*reply,
    335                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    336                                          "text/html"));
    337   return GNUNET_OK;
    338 }
    339 
    340 
    341 enum GNUNET_GenericReturnValue
    342 TALER_TEMPLATING_reply (struct MHD_Connection *connection,
    343                         unsigned int http_status,
    344                         const char *template,
    345                         const char *instance_id,
    346                         const char *taler_uri,
    347                         const json_t *root)
    348 {
    349   enum GNUNET_GenericReturnValue res;
    350   struct MHD_Response *reply;
    351   MHD_RESULT ret;
    352 
    353   res = TALER_TEMPLATING_build (connection,
    354                                 &http_status,
    355                                 template,
    356                                 instance_id,
    357                                 taler_uri,
    358                                 root,
    359                                 &reply);
    360   if (GNUNET_SYSERR == res)
    361     return res;
    362   ret = MHD_queue_response (connection,
    363                             http_status,
    364                             reply);
    365   MHD_destroy_response (reply);
    366   if (MHD_NO == ret)
    367     return GNUNET_SYSERR;
    368   return (res == GNUNET_OK)
    369     ? GNUNET_OK
    370     : 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 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   if ('.' == filename[0])
    396     return GNUNET_OK;
    397   name = strrchr (filename,
    398                   '/');
    399   if (NULL == name)
    400     name = filename;
    401   else
    402     name++;
    403   lang = strchr (name,
    404                  '.');
    405   if (NULL == lang)
    406     return GNUNET_OK; /* name must include .$LANG */
    407   lang++;
    408   end = strchr (lang,
    409                 '.');
    410   if ( (NULL == end) ||
    411        (0 != strcmp (end,
    412                      ".must")) )
    413     return GNUNET_OK; /* name must end with '.must' */
    414 
    415   /* finally open template */
    416   fd = open (filename,
    417              O_RDONLY);
    418   if (-1 == fd)
    419   {
    420     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    421                               "open",
    422                               filename);
    423 
    424     return GNUNET_SYSERR;
    425   }
    426   if (0 !=
    427       fstat (fd,
    428              &sb))
    429   {
    430     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    431                               "fstat",
    432                               filename);
    433     GNUNET_break (0 == close (fd));
    434     return GNUNET_OK;
    435   }
    436   map = GNUNET_malloc_large (sb.st_size + 1);
    437   if (NULL == map)
    438   {
    439     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    440                          "malloc");
    441     GNUNET_break (0 == close (fd));
    442     return GNUNET_SYSERR;
    443   }
    444   if (sb.st_size !=
    445       read (fd,
    446             map,
    447             sb.st_size))
    448   {
    449     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    450                               "read",
    451                               filename);
    452     GNUNET_break (0 == close (fd));
    453     return GNUNET_OK;
    454   }
    455   GNUNET_break (0 == close (fd));
    456   GNUNET_array_grow (loaded,
    457                      loaded_length,
    458                      loaded_length + 1);
    459   loaded[loaded_length - 1].name = GNUNET_strndup (name,
    460                                                    (lang - 1) - name);
    461   loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
    462                                                    end - lang);
    463   loaded[loaded_length - 1].value = map;
    464   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    465               "Loading template `%s' (%s)\n",
    466               filename,
    467               loaded[loaded_length - 1].name);
    468   return GNUNET_OK;
    469 }
    470 
    471 
    472 MHD_RESULT
    473 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   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   ret = TALER_TEMPLATING_reply (connection,
    493                                 http_status,
    494                                 template_basename,
    495                                 NULL,
    496                                 NULL,
    497                                 data);
    498   json_decref (data);
    499   switch (ret)
    500   {
    501   case GNUNET_OK:
    502     return MHD_YES;
    503   case GNUNET_NO:
    504     return MHD_YES;
    505   case GNUNET_SYSERR:
    506     return MHD_NO;
    507   }
    508   GNUNET_assert (0);
    509   return MHD_NO;
    510 }
    511 
    512 
    513 enum GNUNET_GenericReturnValue
    514 TALER_TEMPLATING_init (const struct GNUNET_OS_ProjectData *pd)
    515 {
    516   char *dn;
    517   int ret;
    518 
    519   {
    520     char *path;
    521 
    522     path = GNUNET_OS_installation_get_path (pd,
    523                                             GNUNET_OS_IPK_DATADIR);
    524     if (NULL == path)
    525     {
    526       GNUNET_break (0);
    527       return GNUNET_SYSERR;
    528     }
    529     GNUNET_asprintf (&dn,
    530                      "%s/templates/",
    531                      path);
    532     GNUNET_free (path);
    533   }
    534   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    535               "Loading templates from `%s'\n",
    536               dn);
    537   ret = GNUNET_DISK_directory_scan (dn,
    538                                     &load_template,
    539                                     NULL);
    540   GNUNET_free (dn);
    541   if (-1 == ret)
    542   {
    543     GNUNET_break (0);
    544     return GNUNET_SYSERR;
    545   }
    546   return GNUNET_OK;
    547 }
    548 
    549 
    550 void
    551 TALER_TEMPLATING_done (void)
    552 {
    553   for (unsigned int i = 0; i<loaded_length; i++)
    554   {
    555     GNUNET_free (loaded[i].name);
    556     GNUNET_free (loaded[i].lang);
    557     GNUNET_free (loaded[i].value);
    558   }
    559   GNUNET_array_grow (loaded,
    560                      loaded_length,
    561                      0);
    562 }
    563 
    564 
    565 /* end of templating_api.c */