LCOV - code coverage report
Current view: top level - mhd - mhd_legal.c (source / functions) Hit Total Coverage
Test: GNU Taler coverage report Lines: 126 176 71.6 %
Date: 2021-04-12 06:08:44 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2019, 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 Affero 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 Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file mhd_legal.c
      18             :  * @brief API for returning legal documents based on client language
      19             :  *        and content type preferences
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <gnunet/gnunet_util_lib.h>
      24             : #include <gnunet/gnunet_json_lib.h>
      25             : #include <jansson.h>
      26             : #include <microhttpd.h>
      27             : #include "taler_util.h"
      28             : #include "taler_mhd_lib.h"
      29             : 
      30             : 
      31             : /**
      32             :  * Entry in the terms-of-service array.
      33             :  */
      34             : struct Terms
      35             : {
      36             :   /**
      37             :    * Mime type of the terms.
      38             :    */
      39             :   const char *mime_type;
      40             : 
      41             :   /**
      42             :    * The terms (NOT 0-terminated!), mmap()'ed. Do not free,
      43             :    * use munmap() instead.
      44             :    */
      45             :   void *terms;
      46             : 
      47             :   /**
      48             :    * The desired language.
      49             :    */
      50             :   char *language;
      51             : 
      52             :   /**
      53             :    * deflated @e terms, to return if client supports deflate compression.
      54             :    * malloc()'ed.  NULL if @e terms does not compress.
      55             :    */
      56             :   void *compressed_terms;
      57             : 
      58             :   /**
      59             :    * Number of bytes in @e terms.
      60             :    */
      61             :   size_t terms_size;
      62             : 
      63             :   /**
      64             :    * Number of bytes in @e compressed_terms.
      65             :    */
      66             :   size_t compressed_terms_size;
      67             : 
      68             : 
      69             : };
      70             : 
      71             : 
      72             : /**
      73             :  * Prepared responses for legal documents
      74             :  * (terms of service, privacy policy).
      75             :  */
      76             : struct TALER_MHD_Legal
      77             : {
      78             :   /**
      79             :    * Array of terms of service, terminated by NULL/0 value.
      80             :    */
      81             :   struct Terms *terms;
      82             : 
      83             :   /**
      84             :    * Length of the #terms array.
      85             :    */
      86             :   unsigned int terms_len;
      87             : 
      88             :   /**
      89             :    * Etag to use for the terms of service (= version).
      90             :    */
      91             :   char *terms_etag;
      92             : };
      93             : 
      94             : 
      95             : /**
      96             :  * Check if @a mime matches the @a accept_pattern.
      97             :  *
      98             :  * @param accept_pattern a mime pattern like "text/plain"
      99             :  *        or "image/STAR"
     100             :  * @param mime the mime type to match
     101             :  * @return true if @a mime matches the @a accept_pattern
     102             :  */
     103             : static bool
     104          54 : mime_matches (const char *accept_pattern,
     105             :               const char *mime)
     106             : {
     107          54 :   const char *da = strchr (accept_pattern, '/');
     108          54 :   const char *dm = strchr (mime, '/');
     109             : 
     110          54 :   if ( (NULL == da) ||
     111             :        (NULL == dm) )
     112           0 :     return (0 == strcmp ("*", accept_pattern));
     113             :   return
     114          96 :     ( ( (1 == da - accept_pattern) &&
     115          42 :         ('*' == *accept_pattern) ) ||
     116          12 :       ( (da - accept_pattern == dm - mime) &&
     117           8 :         (0 == strncasecmp (accept_pattern,
     118             :                            mime,
     119         116 :                            da - accept_pattern)) ) ) &&
     120          50 :     ( (0 == strcmp (da, "/*")) ||
     121          10 :       (0 == strcasecmp (da,
     122             :                         dm)) );
     123             : }
     124             : 
     125             : 
     126             : bool
     127          54 : TALER_MHD_xmime_matches (const char *accept_pattern,
     128             :                          const char *mime)
     129             : {
     130          54 :   char *ap = GNUNET_strdup (accept_pattern);
     131             :   char *sptr;
     132             : 
     133          66 :   for (const char *tok = strtok_r (ap, ";", &sptr);
     134             :        NULL != tok;
     135          12 :        tok = strtok_r (NULL, ";", &sptr))
     136             :   {
     137          54 :     if (mime_matches (tok,
     138             :                       mime))
     139             :     {
     140          42 :       GNUNET_free (ap);
     141          42 :       return true;
     142             :     }
     143             :   }
     144          12 :   GNUNET_free (ap);
     145          12 :   return false;
     146             : }
     147             : 
     148             : 
     149             : MHD_RESULT
     150           8 : TALER_MHD_reply_legal (struct MHD_Connection *conn,
     151             :                        struct TALER_MHD_Legal *legal)
     152             : {
     153             :   struct MHD_Response *resp;
     154             :   struct Terms *t;
     155             : 
     156           8 :   if (NULL != legal)
     157             :   {
     158             :     const char *etag;
     159             : 
     160           8 :     etag = MHD_lookup_connection_value (conn,
     161             :                                         MHD_HEADER_KIND,
     162             :                                         MHD_HTTP_HEADER_IF_NONE_MATCH);
     163           8 :     if ( (NULL != etag) &&
     164           0 :          (NULL != legal->terms_etag) &&
     165           0 :          (0 == strcasecmp (etag,
     166           0 :                            legal->terms_etag)) )
     167             :     {
     168             :       MHD_RESULT ret;
     169             : 
     170           0 :       resp = MHD_create_response_from_buffer (0,
     171             :                                               NULL,
     172             :                                               MHD_RESPMEM_PERSISTENT);
     173           0 :       TALER_MHD_add_global_headers (resp);
     174           0 :       ret = MHD_queue_response (conn,
     175             :                                 MHD_HTTP_NOT_MODIFIED,
     176             :                                 resp);
     177           0 :       GNUNET_break (MHD_YES == ret);
     178           0 :       MHD_destroy_response (resp);
     179           0 :       return ret;
     180             :     }
     181             :   }
     182             : 
     183           8 :   t = NULL;
     184           8 :   if (NULL != legal)
     185             :   {
     186             :     const char *mime;
     187             :     const char *lang;
     188             : 
     189           8 :     mime = MHD_lookup_connection_value (conn,
     190             :                                         MHD_HEADER_KIND,
     191             :                                         MHD_HTTP_HEADER_ACCEPT);
     192           8 :     if (NULL == mime)
     193           0 :       mime = "text/html";
     194           8 :     lang = MHD_lookup_connection_value (conn,
     195             :                                         MHD_HEADER_KIND,
     196             :                                         MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
     197           8 :     if (NULL == lang)
     198           6 :       lang = "en";
     199             :     /* Find best match: must match mime type (if possible), and if
     200             :        mime type matches, ideally also language */
     201          48 :     for (unsigned int i = 0; i < legal->terms_len; i++)
     202             :     {
     203          40 :       struct Terms *p = &legal->terms[i];
     204             : 
     205          72 :       if ( (NULL == t) ||
     206          32 :            (TALER_MHD_xmime_matches (mime,
     207             :                                      p->mime_type)) )
     208             :       {
     209          30 :         if ( (NULL == t) ||
     210          22 :              (! TALER_MHD_xmime_matches (mime,
     211          20 :                                          t->mime_type)) ||
     212          20 :              (TALER_language_matches (lang,
     213          20 :                                       p->language) >
     214          20 :               TALER_language_matches (lang,
     215          20 :                                       t->language) ) )
     216          10 :           t = p;
     217             :       }
     218             :     }
     219           8 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     220             :                 "Best match for %s/%s: %s / %s\n",
     221             :                 lang,
     222             :                 mime,
     223             :                 (NULL != t) ? t->mime_type : "<none>",
     224             :                 (NULL != t) ? t->language : "<none>");
     225             :   }
     226             : 
     227           8 :   if (NULL == t)
     228             :   {
     229             :     /* Default terms of service if none are configured */
     230             :     static struct Terms none = {
     231             :       .mime_type = "text/plain",
     232             :       .terms = "not configured",
     233             :       .language = "en",
     234             :       .terms_size = strlen ("not configured")
     235             :     };
     236             : 
     237           0 :     t = &none;
     238             :   }
     239             : 
     240             :   /* try to compress the response */
     241           8 :   resp = NULL;
     242           8 :   if (MHD_YES ==
     243           8 :       TALER_MHD_can_compress (conn))
     244             :   {
     245           0 :     resp = MHD_create_response_from_buffer (t->compressed_terms_size,
     246             :                                             t->compressed_terms,
     247             :                                             MHD_RESPMEM_PERSISTENT);
     248           0 :     if (MHD_NO ==
     249           0 :         MHD_add_response_header (resp,
     250             :                                  MHD_HTTP_HEADER_CONTENT_ENCODING,
     251             :                                  "deflate"))
     252             :     {
     253           0 :       GNUNET_break (0);
     254           0 :       MHD_destroy_response (resp);
     255           0 :       resp = NULL;
     256             :     }
     257             :   }
     258           8 :   if (NULL == resp)
     259             :   {
     260             :     /* could not generate compressed response, return uncompressed */
     261           8 :     resp = MHD_create_response_from_buffer (t->terms_size,
     262             :                                             (void *) t->terms,
     263             :                                             MHD_RESPMEM_PERSISTENT);
     264             :   }
     265           8 :   TALER_MHD_add_global_headers (resp);
     266           8 :   if (NULL != legal)
     267           8 :     GNUNET_break (MHD_YES ==
     268             :                   MHD_add_response_header (resp,
     269             :                                            MHD_HTTP_HEADER_ETAG,
     270             :                                            legal->terms_etag));
     271           8 :   GNUNET_break (MHD_YES ==
     272             :                 MHD_add_response_header (resp,
     273             :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     274             :                                          t->mime_type));
     275             :   {
     276             :     MHD_RESULT ret;
     277             : 
     278           8 :     ret = MHD_queue_response (conn,
     279             :                               MHD_HTTP_OK,
     280             :                               resp);
     281           8 :     MHD_destroy_response (resp);
     282           8 :     return ret;
     283             :   }
     284             : }
     285             : 
     286             : 
     287             : /**
     288             :  * Load all the terms of service from @a path under language @a lang
     289             :  * from file @a name
     290             :  *
     291             :  * @param[in,out] legal where to write the result
     292             :  * @param path where the terms are found
     293             :  * @param lang which language directory to crawl
     294             :  * @param name specific file to access
     295             :  */
     296             : static void
     297          24 : load_terms (struct TALER_MHD_Legal *legal,
     298             :             const char *path,
     299             :             const char *lang,
     300             :             const char *name)
     301             : {
     302             :   static struct MimeMap
     303             :   {
     304             :     const char *ext;
     305             :     const char *mime;
     306             :   } mm[] = {
     307             :     { .ext = ".html", .mime = "text/html" },
     308             :     { .ext = ".htm", .mime = "text/html" },
     309             :     { .ext = ".txt", .mime = "text/plain" },
     310             :     { .ext = ".pdf", .mime = "application/pdf" },
     311             :     { .ext = ".jpg", .mime = "image/jpeg" },
     312             :     { .ext = ".jpeg", .mime = "image/jpeg" },
     313             :     { .ext = ".png", .mime = "image/png" },
     314             :     { .ext = ".gif", .mime = "image/gif" },
     315             :     { .ext = ".epub", .mime = "application/epub+zip" },
     316             :     { .ext = ".xml", .mime = "text/xml" },
     317             :     { .ext = NULL, .mime = NULL }
     318             :   };
     319          24 :   const char *ext = strrchr (name, '.');
     320             :   const char *mime;
     321             : 
     322          24 :   if (NULL == ext)
     323             :   {
     324           4 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     325             :                 "Unsupported file `%s' in directory `%s/%s': lacks extension\n",
     326             :                 name,
     327             :                 path,
     328             :                 lang);
     329           4 :     return;
     330             :   }
     331          20 :   if ( (NULL == legal->terms_etag) ||
     332          20 :        (0 != strncmp (legal->terms_etag,
     333             :                       name,
     334          20 :                       ext - name - 1)) )
     335             :   {
     336           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     337             :                 "Filename `%s' does not match Etag `%s' in directory `%s/%s'. Ignoring it.\n",
     338             :                 name,
     339             :                 legal->terms_etag,
     340             :                 path,
     341             :                 lang);
     342           0 :     return;
     343             :   }
     344          20 :   mime = NULL;
     345         108 :   for (unsigned int i = 0; NULL != mm[i].ext; i++)
     346         108 :     if (0 == strcasecmp (mm[i].ext,
     347             :                          ext))
     348             :     {
     349          20 :       mime = mm[i].mime;
     350          20 :       break;
     351             :     }
     352          20 :   if (NULL == mime)
     353             :   {
     354           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     355             :                 "Unsupported file extension `%s' of file `%s' in directory `%s/%s'\n",
     356             :                 ext,
     357             :                 name,
     358             :                 path,
     359             :                 lang);
     360           0 :     return;
     361             :   }
     362             :   /* try to read the file with the terms of service */
     363             :   {
     364             :     struct stat st;
     365             :     char *fn;
     366             :     int fd;
     367             : 
     368          20 :     GNUNET_asprintf (&fn,
     369             :                      "%s/%s/%s",
     370             :                      path,
     371             :                      lang,
     372             :                      name);
     373          20 :     fd = open (fn, O_RDONLY);
     374          20 :     if (-1 == fd)
     375             :     {
     376           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     377             :                                 "open",
     378             :                                 fn);
     379           0 :       GNUNET_free (fn);
     380           0 :       return;
     381             :     }
     382          20 :     if (0 != fstat (fd, &st))
     383             :     {
     384           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     385             :                                 "fstat",
     386             :                                 fn);
     387           0 :       GNUNET_break (0 == close (fd));
     388           0 :       GNUNET_free (fn);
     389           0 :       return;
     390             :     }
     391             :     if (SIZE_MAX < ((unsigned long long) st.st_size))
     392             :     {
     393             :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     394             :                                 "fstat-size",
     395             :                                 fn);
     396             :       GNUNET_break (0 == close (fd));
     397             :       GNUNET_free (fn);
     398             :       return;
     399             :     }
     400             :     {
     401             :       void *buf;
     402             :       size_t bsize;
     403             : 
     404          20 :       bsize = (size_t) st.st_size;
     405          20 :       buf = mmap (NULL,
     406             :                   bsize,
     407             :                   PROT_READ,
     408             :                   MAP_SHARED,
     409             :                   fd,
     410             :                   0);
     411          20 :       if (MAP_FAILED == buf)
     412             :       {
     413           0 :         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     414             :                                   "mmap",
     415             :                                   fn);
     416           0 :         GNUNET_break (0 == close (fd));
     417           0 :         GNUNET_free (fn);
     418           0 :         return;
     419             :       }
     420          20 :       GNUNET_break (0 == close (fd));
     421          20 :       GNUNET_free (fn);
     422             : 
     423             :       /* append to global list of terms of service */
     424             :       {
     425          40 :         struct Terms t = {
     426             :           .mime_type = mime,
     427             :           .terms = buf,
     428          20 :           .language = GNUNET_strdup (lang),
     429             :           .terms_size = bsize
     430             :         };
     431             : 
     432          20 :         buf = GNUNET_memdup (t.terms,
     433             :                              t.terms_size);
     434          20 :         if (TALER_MHD_body_compress (&buf,
     435             :                                      &bsize))
     436             :         {
     437          20 :           t.compressed_terms = buf;
     438          20 :           t.compressed_terms_size = bsize;
     439             :         }
     440             :         else
     441             :         {
     442           0 :           GNUNET_free (buf);
     443             :         }
     444          20 :         GNUNET_array_append (legal->terms,
     445             :                              legal->terms_len,
     446             :                              t);
     447             :       }
     448             :     }
     449             :   }
     450             : }
     451             : 
     452             : 
     453             : /**
     454             :  * Load all the terms of service from @a path under language @a lang.
     455             :  *
     456             :  * @param[in,out] legal where to write the result
     457             :  * @param path where the terms are found
     458             :  * @param lang which language directory to crawl
     459             :  */
     460             : static void
     461          24 : load_language (struct TALER_MHD_Legal *legal,
     462             :                const char *path,
     463             :                const char *lang)
     464             : {
     465             :   char *dname;
     466             :   DIR *d;
     467             : 
     468          24 :   GNUNET_asprintf (&dname,
     469             :                    "%s/%s",
     470             :                    path,
     471             :                    lang);
     472          24 :   d = opendir (dname);
     473          24 :   if (NULL == d)
     474             :   {
     475          16 :     GNUNET_free (dname);
     476          16 :     return;
     477             :   }
     478          48 :   for (struct dirent *de = readdir (d);
     479             :        NULL != de;
     480          40 :        de = readdir (d))
     481             :   {
     482          40 :     const char *fn = de->d_name;
     483             : 
     484          40 :     if (fn[0] == '.')
     485          16 :       continue;
     486          24 :     load_terms (legal, path, lang, fn);
     487             :   }
     488           8 :   GNUNET_break (0 == closedir (d));
     489           8 :   GNUNET_free (dname);
     490             : }
     491             : 
     492             : 
     493             : struct TALER_MHD_Legal *
     494          22 : TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg,
     495             :                       const char *section,
     496             :                       const char *diroption,
     497             :                       const char *tagoption)
     498             : {
     499             :   struct TALER_MHD_Legal *legal;
     500             :   char *path;
     501             :   DIR *d;
     502             : 
     503          22 :   legal = GNUNET_new (struct TALER_MHD_Legal);
     504          22 :   if (GNUNET_OK !=
     505          22 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     506             :                                              section,
     507             :                                              tagoption,
     508             :                                              &legal->terms_etag))
     509             :   {
     510          12 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     511             :                                section,
     512             :                                tagoption);
     513          12 :     GNUNET_free (legal);
     514          12 :     return NULL;
     515             :   }
     516          10 :   if (GNUNET_OK !=
     517          10 :       GNUNET_CONFIGURATION_get_value_filename (cfg,
     518             :                                                section,
     519             :                                                diroption,
     520             :                                                &path))
     521             :   {
     522           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     523             :                                section,
     524             :                                diroption);
     525           0 :     GNUNET_free (legal->terms_etag);
     526           0 :     GNUNET_free (legal);
     527           0 :     return NULL;
     528             :   }
     529          10 :   d = opendir (path);
     530          10 :   if (NULL == d)
     531             :   {
     532           6 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
     533             :                                section,
     534             :                                diroption,
     535             :                                "Could not open directory");
     536           6 :     GNUNET_free (legal->terms_etag);
     537           6 :     GNUNET_free (legal);
     538           6 :     GNUNET_free (path);
     539           6 :     return NULL;
     540             :   }
     541          40 :   for (struct dirent *de = readdir (d);
     542             :        NULL != de;
     543          36 :        de = readdir (d))
     544             :   {
     545          36 :     const char *lang = de->d_name;
     546             : 
     547          36 :     if (lang[0] == '.')
     548          12 :       continue;
     549          24 :     load_language (legal, path, lang);
     550             :   }
     551           4 :   GNUNET_break (0 == closedir (d));
     552           4 :   GNUNET_free (path);
     553           4 :   return legal;
     554             : }
     555             : 
     556             : 
     557             : void
     558           0 : TALER_MHD_legal_free (struct TALER_MHD_Legal *legal)
     559             : {
     560           0 :   if (NULL == legal)
     561           0 :     return;
     562           0 :   for (unsigned int i = 0; i<legal->terms_len; i++)
     563             :   {
     564           0 :     struct Terms *t = &legal->terms[i];
     565             : 
     566           0 :     GNUNET_free (t->language);
     567           0 :     GNUNET_free (t->compressed_terms);
     568           0 :     if (0 != munmap (t->terms, t->terms_size))
     569           0 :       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     570             :                            "munmap");
     571             :   }
     572           0 :   GNUNET_array_grow (legal->terms,
     573             :                      legal->terms_len,
     574             :                      0);
     575           0 :   GNUNET_free (legal->terms_etag);
     576           0 :   GNUNET_free (legal);
     577             : }

Generated by: LCOV version 1.14