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


      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       GNUNET_break (0 ==
    271                     json_object_set_new ((json_t *) root,
    272                                          "static_url",
    273                                          json_string (static_url)));
    274       GNUNET_free (static_url);
    275     }
    276     if (0 !=
    277         (eno = mustach_jansson_mem (tmpl,
    278                                     0,
    279                                     (json_t *) root,
    280                                     Mustach_With_NoExtensions,
    281                                     &body,
    282                                     &body_size)))
    283     {
    284       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    285                   "mustach failed on template `%s' with error %d\n",
    286                   template,
    287                   eno);
    288       *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    289       *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
    290                                      template);
    291       return GNUNET_NO;
    292     }
    293   }
    294 
    295   /* try to compress reply if client allows it */
    296   {
    297     bool compressed = false;
    298 
    299     if (TALER_MHD_CT_DEFLATE ==
    300         TALER_MHD_can_compress (connection,
    301                                 TALER_MHD_CT_DEFLATE))
    302     {
    303       compressed = TALER_MHD_body_compress ((void **) &body,
    304                                             &body_size);
    305     }
    306     *reply = MHD_create_response_from_buffer (body_size,
    307                                               body,
    308                                               MHD_RESPMEM_MUST_FREE);
    309     if (NULL == *reply)
    310     {
    311       GNUNET_break (0);
    312       return GNUNET_SYSERR;
    313     }
    314     if (compressed)
    315     {
    316       if (MHD_NO ==
    317           MHD_add_response_header (*reply,
    318                                    MHD_HTTP_HEADER_CONTENT_ENCODING,
    319                                    "deflate"))
    320       {
    321         GNUNET_break (0);
    322         MHD_destroy_response (*reply);
    323         *reply = NULL;
    324         return GNUNET_SYSERR;
    325       }
    326     }
    327   }
    328   if (NULL != tve->lang)
    329     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   if (NULL != taler_uri)
    336     GNUNET_break (MHD_NO !=
    337                   MHD_add_response_header (*reply,
    338                                            "Taler",
    339                                            taler_uri));
    340   return GNUNET_OK;
    341 }
    342 
    343 
    344 enum GNUNET_GenericReturnValue
    345 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   res = TALER_TEMPLATING_build (connection,
    357                                 &http_status,
    358                                 template,
    359                                 instance_id,
    360                                 taler_uri,
    361                                 root,
    362                                 &reply);
    363   if (GNUNET_SYSERR == res)
    364     return res;
    365   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   ret = MHD_queue_response (connection,
    371                             http_status,
    372                             reply);
    373   MHD_destroy_response (reply);
    374   if (MHD_NO == ret)
    375     return GNUNET_SYSERR;
    376   return (res == GNUNET_OK)
    377     ? GNUNET_OK
    378     : 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 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   if ('.' == filename[0])
    404     return GNUNET_OK;
    405   name = strrchr (filename,
    406                   '/');
    407   if (NULL == name)
    408     name = filename;
    409   else
    410     name++;
    411   lang = strchr (name,
    412                  '.');
    413   if (NULL == lang)
    414     return GNUNET_OK; /* name must include .$LANG */
    415   lang++;
    416   end = strchr (lang,
    417                 '.');
    418   if ( (NULL == end) ||
    419        (0 != strcmp (end,
    420                      ".must")) )
    421     return GNUNET_OK; /* name must end with '.must' */
    422 
    423   /* finally open template */
    424   fd = open (filename,
    425              O_RDONLY);
    426   if (-1 == fd)
    427   {
    428     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    429                               "open",
    430                               filename);
    431 
    432     return GNUNET_SYSERR;
    433   }
    434   if (0 !=
    435       fstat (fd,
    436              &sb))
    437   {
    438     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    439                               "fstat",
    440                               filename);
    441     GNUNET_break (0 == close (fd));
    442     return GNUNET_OK;
    443   }
    444   map = GNUNET_malloc_large (sb.st_size + 1);
    445   if (NULL == map)
    446   {
    447     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    448                          "malloc");
    449     GNUNET_break (0 == close (fd));
    450     return GNUNET_SYSERR;
    451   }
    452   if (sb.st_size !=
    453       read (fd,
    454             map,
    455             sb.st_size))
    456   {
    457     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    458                               "read",
    459                               filename);
    460     GNUNET_break (0 == close (fd));
    461     return GNUNET_OK;
    462   }
    463   GNUNET_break (0 == close (fd));
    464   GNUNET_array_grow (loaded,
    465                      loaded_length,
    466                      loaded_length + 1);
    467   loaded[loaded_length - 1].name = GNUNET_strndup (name,
    468                                                    (lang - 1) - name);
    469   loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
    470                                                    end - lang);
    471   loaded[loaded_length - 1].value = map;
    472   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    473               "Loading template `%s' (%s)\n",
    474               filename,
    475               loaded[loaded_length - 1].name);
    476   return GNUNET_OK;
    477 }
    478 
    479 
    480 enum MHD_Result
    481 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   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   ret = TALER_TEMPLATING_reply (connection,
    501                                 http_status,
    502                                 template_basename,
    503                                 NULL,
    504                                 NULL,
    505                                 data);
    506   json_decref (data);
    507   switch (ret)
    508   {
    509   case GNUNET_OK:
    510     return MHD_YES;
    511   case GNUNET_NO:
    512     return MHD_YES;
    513   case GNUNET_SYSERR:
    514     return MHD_NO;
    515   }
    516   GNUNET_assert (0);
    517   return MHD_NO;
    518 }
    519 
    520 
    521 enum GNUNET_GenericReturnValue
    522 TALER_TEMPLATING_init (const struct GNUNET_OS_ProjectData *pd)
    523 {
    524   char *dn;
    525   int ret;
    526 
    527   {
    528     char *path;
    529 
    530     path = GNUNET_OS_installation_get_path (pd,
    531                                             GNUNET_OS_IPK_DATADIR);
    532     if (NULL == path)
    533     {
    534       GNUNET_break (0);
    535       return GNUNET_SYSERR;
    536     }
    537     GNUNET_asprintf (&dn,
    538                      "%s/templates/",
    539                      path);
    540     GNUNET_free (path);
    541   }
    542   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    543               "Loading templates from `%s'\n",
    544               dn);
    545   ret = GNUNET_DISK_directory_scan (dn,
    546                                     &load_template,
    547                                     NULL);
    548   GNUNET_free (dn);
    549   if (-1 == ret)
    550   {
    551     GNUNET_break (0);
    552     return GNUNET_SYSERR;
    553   }
    554   return GNUNET_OK;
    555 }
    556 
    557 
    558 void
    559 TALER_TEMPLATING_done (void)
    560 {
    561   for (unsigned int i = 0; i<loaded_length; i++)
    562   {
    563     GNUNET_free (loaded[i].name);
    564     GNUNET_free (loaded[i].lang);
    565     GNUNET_free (loaded[i].value);
    566   }
    567   GNUNET_array_grow (loaded,
    568                      loaded_length,
    569                      0);
    570 }
    571 
    572 
    573 /* end of templating_api.c */