merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_statics.c (8911B)


      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_statics.c
     18  * @brief logic to load and return static resource files by client language preference
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include "taler_merchant_util.h"
     24 #include <taler/taler_util.h>
     25 #include <taler/taler_mhd_lib.h>
     26 #include <taler/taler_templating_lib.h>
     27 #include "taler-merchant-httpd_statics.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    * Pre-built reply.
     48    */
     49   struct MHD_Response *reply;
     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 struct TVE *
     76 lookup_file (struct MHD_Connection *connection,
     77              const char *name)
     78 {
     79   double best_q = 0.0;
     80   struct TVE *best = NULL;
     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 static file found for `%s'\n",
    109                 name);
    110     return NULL;
    111   }
    112   return best;
    113 }
    114 
    115 
    116 MHD_RESULT
    117 TMH_return_static (const struct TMH_RequestHandler *rh,
    118                    struct MHD_Connection *connection,
    119                    struct TMH_HandlerContext *hc)
    120 {
    121   const struct TVE *tmpl;
    122 
    123   tmpl = lookup_file (connection,
    124                       hc->infix);
    125   if (NULL == tmpl)
    126   {
    127     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    128                 "Failed to load static file `%s'\n",
    129                 hc->infix);
    130     return TALER_MHD_reply_with_error (connection,
    131                                        MHD_HTTP_NOT_FOUND,
    132                                        TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    133                                        hc->infix);
    134   }
    135 
    136   return MHD_queue_response (connection,
    137                              MHD_HTTP_OK,
    138                              tmpl->reply);
    139 }
    140 
    141 
    142 /**
    143  * Function called with a static file's filename.
    144  *
    145  * @param cls closure
    146  * @param filename complete filename (absolute path)
    147  * @return #GNUNET_OK to continue to iterate,
    148  *  #GNUNET_NO to stop iteration with no error,
    149  *  #GNUNET_SYSERR to abort iteration with error!
    150  */
    151 static enum GNUNET_GenericReturnValue
    152 load_static_file (void *cls,
    153                   const char *filename)
    154 {
    155   char *lang;
    156   char *end;
    157   int fd;
    158   struct stat sb;
    159   const char *name;
    160   struct MHD_Response *reply;
    161 
    162   if ('.' == filename[0])
    163     return GNUNET_OK;
    164   name = strrchr (filename,
    165                   '/');
    166   if (NULL == name)
    167     name = filename;
    168   else
    169     name++;
    170   lang = strchr (name,
    171                  '.');
    172   if (NULL == lang)
    173     return GNUNET_OK; /* name must include _some_ extension */
    174   lang++;
    175   end = strchr (lang,
    176                 '.');
    177   if (NULL == end)
    178   {
    179     /* language was not present, we ONLY have the extension */
    180     end = lang - 1;
    181     lang = NULL;
    182   }
    183   /* finally open template */
    184   fd = open (filename,
    185              O_RDONLY);
    186   if (-1 == fd)
    187   {
    188     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    189                               "open",
    190                               filename);
    191     return GNUNET_SYSERR;
    192   }
    193   if (0 !=
    194       fstat (fd,
    195              &sb))
    196   {
    197     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    198                               "open",
    199                               filename);
    200     GNUNET_break (0 == close (fd));
    201     return GNUNET_OK;
    202   }
    203 
    204   reply = MHD_create_response_from_fd (sb.st_size,
    205                                        fd);
    206   if (NULL == reply)
    207   {
    208     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    209                               "open",
    210                               filename);
    211     GNUNET_break (0 == close (fd));
    212     return GNUNET_OK;
    213   }
    214 
    215   {
    216     static struct MimeMap
    217     {
    218       const char *ext;
    219       const char *mime;
    220     } mm[] = {
    221       { .ext = ".css", .mime = "text/css" },
    222       { .ext = ".js", .mime = "text/javascript" },
    223       { .ext = ".html", .mime = "text/html" },
    224       { .ext = ".htm", .mime = "text/html" },
    225       { .ext = ".txt", .mime = "text/plain" },
    226       { .ext = ".pdf", .mime = "application/pdf" },
    227       { .ext = ".jpg", .mime = "image/jpeg" },
    228       { .ext = ".jpeg", .mime = "image/jpeg" },
    229       { .ext = ".png", .mime = "image/png" },
    230       { .ext = ".apng", .mime = "image/apng" },
    231       { .ext = ".gif", .mime = "image/gif" },
    232       { .ext = ".svg", .mime = "image/svg+xml" },
    233       { .ext = ".tiff", .mime = "image/tiff" },
    234       { .ext = ".ico", .mime = "image/x-icon" },
    235       { .ext = ".bmp", .mime = "image/bmp" },
    236       { .ext = ".epub", .mime = "application/epub+zip" },
    237       { .ext = ".xml", .mime = "text/xml" },
    238       { .ext = NULL, .mime = NULL }
    239     };
    240     const char *mime;
    241 
    242     mime = NULL;
    243     for (unsigned int i = 0; NULL != mm[i].ext; i++)
    244       if (0 == strcasecmp (mm[i].ext,
    245                            end))
    246       {
    247         mime = mm[i].mime;
    248         break;
    249       }
    250     if (NULL != mime)
    251       GNUNET_break (MHD_NO !=
    252                     MHD_add_response_header (reply,
    253                                              MHD_HTTP_HEADER_CONTENT_TYPE,
    254                                              mime));
    255   }
    256 
    257   GNUNET_array_grow (loaded,
    258                      loaded_length,
    259                      loaded_length + 1);
    260   if (NULL != lang)
    261   {
    262     GNUNET_asprintf (&loaded[loaded_length - 1].name,
    263                      "%.*s%s",
    264                      (int) (lang - name) - 1,
    265                      name,
    266                      end);
    267     loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
    268                                                      end - lang);
    269   }
    270   else
    271   {
    272     loaded[loaded_length - 1].name = GNUNET_strdup (name);
    273   }
    274   loaded[loaded_length - 1].reply = reply;
    275   return GNUNET_OK;
    276 }
    277 
    278 
    279 /**
    280  * Preload static files.
    281  */
    282 enum GNUNET_GenericReturnValue
    283 TMH_statics_init ()
    284 {
    285   char *dn;
    286   int ret;
    287 
    288   {
    289     char *path;
    290 
    291     path = GNUNET_OS_installation_get_path (TALER_MERCHANT_project_data (),
    292                                             GNUNET_OS_IPK_DATADIR);
    293     if (NULL == path)
    294     {
    295       GNUNET_break (0);
    296       return GNUNET_SYSERR;
    297     }
    298     GNUNET_asprintf (&dn,
    299                      "%smerchant/static/",
    300                      path);
    301     GNUNET_free (path);
    302   }
    303   ret = GNUNET_DISK_directory_scan (dn,
    304                                     &load_static_file,
    305                                     NULL);
    306   if (-1 == ret)
    307   {
    308     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    309                 "Could not load static resources from `%s': %s\n",
    310                 dn,
    311                 strerror (errno));
    312     GNUNET_free (dn);
    313     return GNUNET_SYSERR;
    314   }
    315   GNUNET_free (dn);
    316   return GNUNET_OK;
    317 }
    318 
    319 
    320 /**
    321  * Nicely shut down.
    322  */
    323 void __attribute__ ((destructor))
    324 get_statics_fini (void);
    325 
    326 /* Declaration avoids compiler warning */
    327 void __attribute__ ((destructor))
    328 get_statics_fini ()
    329 {
    330   for (unsigned int i = 0; i<loaded_length; i++)
    331   {
    332     GNUNET_free (loaded[i].name);
    333     GNUNET_free (loaded[i].lang);
    334     MHD_destroy_response (loaded[i].reply);
    335   }
    336   GNUNET_array_grow (loaded,
    337                      loaded_length,
    338                      0);
    339 }