LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_statics.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 4 95 4.2 %
Date: 2025-06-23 16:22:09 Functions: 1 5 20.0 %

          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_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           0 : lookup_file (struct MHD_Connection *connection,
      77             :              const char *name)
      78             : {
      79           0 :   double best_q = 0.0;
      80           0 :   struct TVE *best = NULL;
      81             :   const char *lang;
      82             : 
      83           0 :   lang = MHD_lookup_connection_value (connection,
      84             :                                       MHD_HEADER_KIND,
      85             :                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
      86           0 :   if (NULL == lang)
      87           0 :     lang = "en";
      88             :   /* find best match by language */
      89           0 :   for (unsigned int i = 0; i<loaded_length; i++)
      90             :   {
      91             :     double q;
      92             : 
      93           0 :     if (0 != strcmp (loaded[i].name,
      94             :                      name))
      95           0 :       continue; /* does not match by name */
      96           0 :     if (NULL == loaded[i].lang) /* no language == always best match */
      97           0 :       return &loaded[i];
      98           0 :     q = TALER_pattern_matches (lang,
      99           0 :                                loaded[i].lang);
     100           0 :     if (q < best_q)
     101           0 :       continue;
     102           0 :     best_q = q;
     103           0 :     best = &loaded[i];
     104             :   }
     105           0 :   if (NULL == best)
     106             :   {
     107           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     108             :                 "No static file found for `%s'\n",
     109             :                 name);
     110           0 :     return NULL;
     111             :   }
     112           0 :   return best;
     113             : }
     114             : 
     115             : 
     116             : MHD_RESULT
     117           0 : 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           0 :   tmpl = lookup_file (connection,
     124           0 :                       hc->infix);
     125           0 :   if (NULL == tmpl)
     126             :   {
     127           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     128             :                 "Failed to load static file `%s'\n",
     129             :                 hc->infix);
     130           0 :     return TALER_MHD_reply_with_error (connection,
     131             :                                        MHD_HTTP_NOT_FOUND,
     132             :                                        TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
     133           0 :                                        hc->infix);
     134             :   }
     135             : 
     136           0 :   return MHD_queue_response (connection,
     137             :                              MHD_HTTP_OK,
     138           0 :                              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           0 : 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           0 :   if ('.' == filename[0])
     163           0 :     return GNUNET_OK;
     164           0 :   name = strrchr (filename,
     165             :                   '/');
     166           0 :   if (NULL == name)
     167           0 :     name = filename;
     168             :   else
     169           0 :     name++;
     170           0 :   lang = strchr (name,
     171             :                  '.');
     172           0 :   if (NULL == lang)
     173           0 :     return GNUNET_OK; /* name must include _some_ extension */
     174           0 :   lang++;
     175           0 :   end = strchr (lang,
     176             :                 '.');
     177           0 :   if (NULL == end)
     178             :   {
     179             :     /* language was not present, we ONLY have the extension */
     180           0 :     end = lang - 1;
     181           0 :     lang = NULL;
     182             :   }
     183             :   /* finally open template */
     184           0 :   fd = open (filename,
     185             :              O_RDONLY);
     186           0 :   if (-1 == fd)
     187             :   {
     188           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     189             :                               "open",
     190             :                               filename);
     191           0 :     return GNUNET_SYSERR;
     192             :   }
     193           0 :   if (0 !=
     194           0 :       fstat (fd,
     195             :              &sb))
     196             :   {
     197           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     198             :                               "open",
     199             :                               filename);
     200           0 :     GNUNET_break (0 == close (fd));
     201           0 :     return GNUNET_OK;
     202             :   }
     203             : 
     204           0 :   reply = MHD_create_response_from_fd (sb.st_size,
     205             :                                        fd);
     206           0 :   if (NULL == reply)
     207             :   {
     208           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     209             :                               "open",
     210             :                               filename);
     211           0 :     GNUNET_break (0 == close (fd));
     212           0 :     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           0 :     mime = NULL;
     243           0 :     for (unsigned int i = 0; NULL != mm[i].ext; i++)
     244           0 :       if (0 == strcasecmp (mm[i].ext,
     245             :                            end))
     246             :       {
     247           0 :         mime = mm[i].mime;
     248           0 :         break;
     249             :       }
     250           0 :     if (NULL != mime)
     251           0 :       GNUNET_break (MHD_NO !=
     252             :                     MHD_add_response_header (reply,
     253             :                                              MHD_HTTP_HEADER_CONTENT_TYPE,
     254             :                                              mime));
     255             :   }
     256             : 
     257           0 :   GNUNET_array_grow (loaded,
     258             :                      loaded_length,
     259             :                      loaded_length + 1);
     260           0 :   if (NULL != lang)
     261             :   {
     262           0 :     GNUNET_asprintf (&loaded[loaded_length - 1].name,
     263             :                      "%.*s%s",
     264           0 :                      (int) (lang - name) - 1,
     265             :                      name,
     266             :                      end);
     267           0 :     loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
     268             :                                                      end - lang);
     269             :   }
     270             :   else
     271             :   {
     272           0 :     loaded[loaded_length - 1].name = GNUNET_strdup (name);
     273             :   }
     274           0 :   loaded[loaded_length - 1].reply = reply;
     275           0 :   return GNUNET_OK;
     276             : }
     277             : 
     278             : 
     279             : /**
     280             :  * Preload static files.
     281             :  */
     282             : enum GNUNET_GenericReturnValue
     283           0 : TMH_statics_init ()
     284             : {
     285             :   char *dn;
     286             :   int ret;
     287             : 
     288             :   {
     289             :     char *path;
     290             : 
     291           0 :     path = GNUNET_OS_installation_get_path (TALER_MERCHANT_project_data (),
     292             :                                             GNUNET_OS_IPK_DATADIR);
     293           0 :     if (NULL == path)
     294             :     {
     295           0 :       GNUNET_break (0);
     296           0 :       return GNUNET_SYSERR;
     297             :     }
     298           0 :     GNUNET_asprintf (&dn,
     299             :                      "%smerchant/static/",
     300             :                      path);
     301           0 :     GNUNET_free (path);
     302             :   }
     303           0 :   ret = GNUNET_DISK_directory_scan (dn,
     304             :                                     &load_static_file,
     305             :                                     NULL);
     306           0 :   if (-1 == ret)
     307             :   {
     308           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     309             :                 "Could not load static resources from `%s': %s\n",
     310             :                 dn,
     311             :                 strerror (errno));
     312           0 :     GNUNET_free (dn);
     313           0 :     return GNUNET_SYSERR;
     314             :   }
     315           0 :   GNUNET_free (dn);
     316           0 :   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          27 : get_statics_fini ()
     329             : {
     330          27 :   for (unsigned int i = 0; i<loaded_length; i++)
     331             :   {
     332           0 :     GNUNET_free (loaded[i].name);
     333           0 :     GNUNET_free (loaded[i].lang);
     334           0 :     MHD_destroy_response (loaded[i].reply);
     335             :   }
     336          27 :   GNUNET_array_grow (loaded,
     337             :                      loaded_length,
     338             :                      0);
     339          27 : }

Generated by: LCOV version 1.16