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 (16055B)


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