LCOV - code coverage report
Current view: top level - mhd - mhd_legal.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 0 196 0.0 %
Date: 2022-08-25 06:15:09 Functions: 0 7 0.0 %
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             :  * How long should browsers/proxies cache the "legal" replies?
      32             :  */
      33             : #define MAX_TERMS_CACHING GNUNET_TIME_UNIT_DAYS
      34             : 
      35             : 
      36             : /**
      37             :  * Entry in the terms-of-service array.
      38             :  */
      39             : struct Terms
      40             : {
      41             :   /**
      42             :    * Kept in a DLL.
      43             :    */
      44             :   struct Terms *prev;
      45             : 
      46             :   /**
      47             :    * Kept in a DLL.
      48             :    */
      49             :   struct Terms *next;
      50             : 
      51             :   /**
      52             :    * Mime type of the terms.
      53             :    */
      54             :   const char *mime_type;
      55             : 
      56             :   /**
      57             :    * The terms (NOT 0-terminated!), mmap()'ed. Do not free,
      58             :    * use munmap() instead.
      59             :    */
      60             :   void *terms;
      61             : 
      62             :   /**
      63             :    * The desired language.
      64             :    */
      65             :   char *language;
      66             : 
      67             :   /**
      68             :    * deflated @e terms, to return if client supports deflate compression.
      69             :    * malloc()'ed.  NULL if @e terms does not compress.
      70             :    */
      71             :   void *compressed_terms;
      72             : 
      73             :   /**
      74             :    * Number of bytes in @e terms.
      75             :    */
      76             :   size_t terms_size;
      77             : 
      78             :   /**
      79             :    * Number of bytes in @e compressed_terms.
      80             :    */
      81             :   size_t compressed_terms_size;
      82             : 
      83             :   /**
      84             :    * Sorting key by format preference in case
      85             :    * everything else is equal. Higher is preferred.
      86             :    */
      87             :   unsigned int priority;
      88             : 
      89             : };
      90             : 
      91             : 
      92             : /**
      93             :  * Prepared responses for legal documents
      94             :  * (terms of service, privacy policy).
      95             :  */
      96             : struct TALER_MHD_Legal
      97             : {
      98             :   /**
      99             :    * DLL of terms of service.
     100             :    */
     101             :   struct Terms *terms_head;
     102             : 
     103             :   /**
     104             :    * DLL of terms of service.
     105             :    */
     106             :   struct Terms *terms_tail;
     107             : 
     108             :   /**
     109             :    * Etag to use for the terms of service (= version).
     110             :    */
     111             :   char *terms_etag;
     112             : };
     113             : 
     114             : 
     115             : /**
     116             :  * Check if @a mime matches the @a accept_pattern.
     117             :  *
     118             :  * @param accept_pattern a mime pattern like "text/plain"
     119             :  *        or "image/STAR"
     120             :  * @param mime the mime type to match
     121             :  * @return true if @a mime matches the @a accept_pattern
     122             :  */
     123             : static bool
     124           0 : mime_matches (const char *accept_pattern,
     125             :               const char *mime)
     126             : {
     127           0 :   const char *da = strchr (accept_pattern, '/');
     128           0 :   const char *dm = strchr (mime, '/');
     129             : 
     130           0 :   if ( (NULL == da) ||
     131             :        (NULL == dm) )
     132           0 :     return (0 == strcmp ("*", accept_pattern));
     133             :   return
     134           0 :     ( ( (1 == da - accept_pattern) &&
     135           0 :         ('*' == *accept_pattern) ) ||
     136           0 :       ( (da - accept_pattern == dm - mime) &&
     137           0 :         (0 == strncasecmp (accept_pattern,
     138             :                            mime,
     139           0 :                            da - accept_pattern)) ) ) &&
     140           0 :     ( (0 == strcmp (da, "/*")) ||
     141           0 :       (0 == strcasecmp (da,
     142             :                         dm)) );
     143             : }
     144             : 
     145             : 
     146             : bool
     147           0 : TALER_MHD_xmime_matches (const char *accept_pattern,
     148             :                          const char *mime)
     149             : {
     150           0 :   char *ap = GNUNET_strdup (accept_pattern);
     151             :   char *sptr;
     152             : 
     153           0 :   for (const char *tok = strtok_r (ap, ";", &sptr);
     154             :        NULL != tok;
     155           0 :        tok = strtok_r (NULL, ";", &sptr))
     156             :   {
     157           0 :     if (mime_matches (tok,
     158             :                       mime))
     159             :     {
     160           0 :       GNUNET_free (ap);
     161           0 :       return true;
     162             :     }
     163             :   }
     164           0 :   GNUNET_free (ap);
     165           0 :   return false;
     166             : }
     167             : 
     168             : 
     169             : MHD_RESULT
     170           0 : TALER_MHD_reply_legal (struct MHD_Connection *conn,
     171             :                        struct TALER_MHD_Legal *legal)
     172             : {
     173             :   struct MHD_Response *resp;
     174             :   struct Terms *t;
     175             :   struct GNUNET_TIME_Absolute a;
     176             :   struct GNUNET_TIME_Timestamp m;
     177             :   char dat[128];
     178             : 
     179           0 :   a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING);
     180           0 :   m = GNUNET_TIME_absolute_to_timestamp (a);
     181           0 :   TALER_MHD_get_date_string (m.abs_time,
     182             :                              dat);
     183           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     184             :               "Setting 'Expires' header to '%s'\n",
     185             :               dat);
     186           0 :   if (NULL != legal)
     187             :   {
     188             :     const char *etag;
     189             : 
     190           0 :     etag = MHD_lookup_connection_value (conn,
     191             :                                         MHD_HEADER_KIND,
     192             :                                         MHD_HTTP_HEADER_IF_NONE_MATCH);
     193           0 :     if ( (NULL != etag) &&
     194           0 :          (NULL != legal->terms_etag) &&
     195           0 :          (0 == strcasecmp (etag,
     196           0 :                            legal->terms_etag)) )
     197             :     {
     198             :       MHD_RESULT ret;
     199             : 
     200           0 :       resp = MHD_create_response_from_buffer (0,
     201             :                                               NULL,
     202             :                                               MHD_RESPMEM_PERSISTENT);
     203           0 :       TALER_MHD_add_global_headers (resp);
     204           0 :       GNUNET_break (MHD_YES ==
     205             :                     MHD_add_response_header (resp,
     206             :                                              MHD_HTTP_HEADER_EXPIRES,
     207             :                                              dat));
     208             : 
     209           0 :       GNUNET_break (MHD_YES ==
     210             :                     MHD_add_response_header (resp,
     211             :                                              MHD_HTTP_HEADER_ETAG,
     212             :                                              legal->terms_etag));
     213           0 :       ret = MHD_queue_response (conn,
     214             :                                 MHD_HTTP_NOT_MODIFIED,
     215             :                                 resp);
     216           0 :       GNUNET_break (MHD_YES == ret);
     217           0 :       MHD_destroy_response (resp);
     218           0 :       return ret;
     219             :     }
     220             :   }
     221             : 
     222           0 :   t = NULL;
     223           0 :   if (NULL != legal)
     224             :   {
     225             :     const char *mime;
     226             :     const char *lang;
     227             : 
     228           0 :     mime = MHD_lookup_connection_value (conn,
     229             :                                         MHD_HEADER_KIND,
     230             :                                         MHD_HTTP_HEADER_ACCEPT);
     231           0 :     if (NULL == mime)
     232           0 :       mime = "text/html";
     233           0 :     lang = MHD_lookup_connection_value (conn,
     234             :                                         MHD_HEADER_KIND,
     235             :                                         MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
     236           0 :     if (NULL == lang)
     237           0 :       lang = "en";
     238             :     /* Find best match: must match mime type (if possible), and if
     239             :        mime type matches, ideally also language */
     240           0 :     for (struct Terms *p = legal->terms_head;
     241             :          NULL != p;
     242           0 :          p = p->next)
     243             :     {
     244           0 :       if ( (NULL == t) ||
     245           0 :            (TALER_MHD_xmime_matches (mime,
     246             :                                      p->mime_type)) )
     247             :       {
     248           0 :         if ( (NULL == t) ||
     249           0 :              (! TALER_MHD_xmime_matches (mime,
     250           0 :                                          t->mime_type)) ||
     251           0 :              (TALER_language_matches (lang,
     252           0 :                                       p->language) >
     253           0 :               TALER_language_matches (lang,
     254           0 :                                       t->language) ) )
     255           0 :           t = p;
     256             :       }
     257             :     }
     258           0 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     259             :                 "Best match for %s/%s: %s / %s\n",
     260             :                 lang,
     261             :                 mime,
     262             :                 (NULL != t) ? t->mime_type : "<none>",
     263             :                 (NULL != t) ? t->language : "<none>");
     264             :   }
     265             : 
     266           0 :   if (NULL == t)
     267             :   {
     268             :     /* Default terms of service if none are configured */
     269             :     static struct Terms none = {
     270             :       .mime_type = "text/plain",
     271             :       .terms = "not configured",
     272             :       .language = "en",
     273             :       .terms_size = strlen ("not configured")
     274             :     };
     275             : 
     276           0 :     t = &none;
     277             :   }
     278             : 
     279             :   /* try to compress the response */
     280           0 :   resp = NULL;
     281           0 :   if (MHD_YES ==
     282           0 :       TALER_MHD_can_compress (conn))
     283             :   {
     284           0 :     resp = MHD_create_response_from_buffer (t->compressed_terms_size,
     285             :                                             t->compressed_terms,
     286             :                                             MHD_RESPMEM_PERSISTENT);
     287           0 :     if (MHD_NO ==
     288           0 :         MHD_add_response_header (resp,
     289             :                                  MHD_HTTP_HEADER_CONTENT_ENCODING,
     290             :                                  "deflate"))
     291             :     {
     292           0 :       GNUNET_break (0);
     293           0 :       MHD_destroy_response (resp);
     294           0 :       resp = NULL;
     295             :     }
     296             :   }
     297           0 :   if (NULL == resp)
     298             :   {
     299             :     /* could not generate compressed response, return uncompressed */
     300           0 :     resp = MHD_create_response_from_buffer (t->terms_size,
     301             :                                             (void *) t->terms,
     302             :                                             MHD_RESPMEM_PERSISTENT);
     303             :   }
     304           0 :   TALER_MHD_add_global_headers (resp);
     305           0 :   GNUNET_break (MHD_YES ==
     306             :                 MHD_add_response_header (resp,
     307             :                                          MHD_HTTP_HEADER_EXPIRES,
     308             :                                          dat));
     309             :   /* Set cache control headers: our response varies depending on these headers */
     310           0 :   GNUNET_break (MHD_YES ==
     311             :                 MHD_add_response_header (resp,
     312             :                                          MHD_HTTP_HEADER_VARY,
     313             :                                          MHD_HTTP_HEADER_ACCEPT_LANGUAGE ","
     314             :                                          MHD_HTTP_HEADER_ACCEPT ","
     315             :                                          MHD_HTTP_HEADER_ACCEPT_ENCODING));
     316             :   /* Information is always public, revalidate after 10 days */
     317           0 :   GNUNET_break (MHD_YES ==
     318             :                 MHD_add_response_header (resp,
     319             :                                          MHD_HTTP_HEADER_CACHE_CONTROL,
     320             :                                          "public max-age=864000"));
     321           0 :   if (NULL != legal)
     322           0 :     GNUNET_break (MHD_YES ==
     323             :                   MHD_add_response_header (resp,
     324             :                                            MHD_HTTP_HEADER_ETAG,
     325             :                                            legal->terms_etag));
     326           0 :   GNUNET_break (MHD_YES ==
     327             :                 MHD_add_response_header (resp,
     328             :                                          MHD_HTTP_HEADER_CONTENT_TYPE,
     329             :                                          t->mime_type));
     330             :   {
     331             :     MHD_RESULT ret;
     332             : 
     333           0 :     ret = MHD_queue_response (conn,
     334             :                               MHD_HTTP_OK,
     335             :                               resp);
     336           0 :     MHD_destroy_response (resp);
     337           0 :     return ret;
     338             :   }
     339             : }
     340             : 
     341             : 
     342             : /**
     343             :  * Load all the terms of service from @a path under language @a lang
     344             :  * from file @a name
     345             :  *
     346             :  * @param[in,out] legal where to write the result
     347             :  * @param path where the terms are found
     348             :  * @param lang which language directory to crawl
     349             :  * @param name specific file to access
     350             :  */
     351             : static void
     352           0 : load_terms (struct TALER_MHD_Legal *legal,
     353             :             const char *path,
     354             :             const char *lang,
     355             :             const char *name)
     356             : {
     357             :   static struct MimeMap
     358             :   {
     359             :     const char *ext;
     360             :     const char *mime;
     361             :     unsigned int priority;
     362             :   } mm[] = {
     363             :     { .ext = ".html", .mime = "text/html", .priority = 100 },
     364             :     { .ext = ".htm", .mime = "text/html", .priority = 99 },
     365             :     { .ext = ".txt", .mime = "text/plain", .priority = 50 },
     366             :     { .ext = ".pdf", .mime = "application/pdf", .priority = 25 },
     367             :     { .ext = ".jpg", .mime = "image/jpeg" },
     368             :     { .ext = ".jpeg", .mime = "image/jpeg" },
     369             :     { .ext = ".png", .mime = "image/png" },
     370             :     { .ext = ".gif", .mime = "image/gif" },
     371             :     { .ext = ".epub", .mime = "application/epub+zip", .priority = 10 },
     372             :     { .ext = ".xml", .mime = "text/xml", .priority = 10 },
     373             :     { .ext = NULL, .mime = NULL }
     374             :   };
     375           0 :   const char *ext = strrchr (name, '.');
     376             :   const char *mime;
     377             :   unsigned int priority;
     378             : 
     379           0 :   if (NULL == ext)
     380             :   {
     381           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     382             :                 "Unsupported file `%s' in directory `%s/%s': lacks extension\n",
     383             :                 name,
     384             :                 path,
     385             :                 lang);
     386           0 :     return;
     387             :   }
     388           0 :   if ( (NULL == legal->terms_etag) ||
     389           0 :        (0 != strncmp (legal->terms_etag,
     390             :                       name,
     391           0 :                       ext - name - 1)) )
     392             :   {
     393           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     394             :                 "Filename `%s' does not match Etag `%s' in directory `%s/%s'. Ignoring it.\n",
     395             :                 name,
     396             :                 legal->terms_etag,
     397             :                 path,
     398             :                 lang);
     399           0 :     return;
     400             :   }
     401           0 :   mime = NULL;
     402           0 :   for (unsigned int i = 0; NULL != mm[i].ext; i++)
     403           0 :     if (0 == strcasecmp (mm[i].ext,
     404             :                          ext))
     405             :     {
     406           0 :       mime = mm[i].mime;
     407           0 :       priority = mm[i].priority;
     408           0 :       break;
     409             :     }
     410           0 :   if (NULL == mime)
     411             :   {
     412           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     413             :                 "Unsupported file extension `%s' of file `%s' in directory `%s/%s'\n",
     414             :                 ext,
     415             :                 name,
     416             :                 path,
     417             :                 lang);
     418           0 :     return;
     419             :   }
     420             :   /* try to read the file with the terms of service */
     421             :   {
     422             :     struct stat st;
     423             :     char *fn;
     424             :     int fd;
     425             : 
     426           0 :     GNUNET_asprintf (&fn,
     427             :                      "%s/%s/%s",
     428             :                      path,
     429             :                      lang,
     430             :                      name);
     431           0 :     fd = open (fn, O_RDONLY);
     432           0 :     if (-1 == fd)
     433             :     {
     434           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     435             :                                 "open",
     436             :                                 fn);
     437           0 :       GNUNET_free (fn);
     438           0 :       return;
     439             :     }
     440           0 :     if (0 != fstat (fd, &st))
     441             :     {
     442           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     443             :                                 "fstat",
     444             :                                 fn);
     445           0 :       GNUNET_break (0 == close (fd));
     446           0 :       GNUNET_free (fn);
     447           0 :       return;
     448             :     }
     449             :     if (SIZE_MAX < ((unsigned long long) st.st_size))
     450             :     {
     451             :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     452             :                                 "fstat-size",
     453             :                                 fn);
     454             :       GNUNET_break (0 == close (fd));
     455             :       GNUNET_free (fn);
     456             :       return;
     457             :     }
     458             :     {
     459             :       void *buf;
     460             :       size_t bsize;
     461             : 
     462           0 :       bsize = (size_t) st.st_size;
     463           0 :       buf = mmap (NULL,
     464             :                   bsize,
     465             :                   PROT_READ,
     466             :                   MAP_SHARED,
     467             :                   fd,
     468             :                   0);
     469           0 :       if (MAP_FAILED == buf)
     470             :       {
     471           0 :         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     472             :                                   "mmap",
     473             :                                   fn);
     474           0 :         GNUNET_break (0 == close (fd));
     475           0 :         GNUNET_free (fn);
     476           0 :         return;
     477             :       }
     478           0 :       GNUNET_break (0 == close (fd));
     479           0 :       GNUNET_free (fn);
     480             : 
     481             :       /* insert into global list of terms of service */
     482             :       {
     483             :         struct Terms *t;
     484             : 
     485           0 :         t = GNUNET_new (struct Terms);
     486           0 :         t->mime_type = mime;
     487           0 :         t->terms = buf;
     488           0 :         t->language = GNUNET_strdup (lang);
     489           0 :         t->terms_size = bsize;
     490           0 :         t->priority = priority;
     491           0 :         buf = GNUNET_memdup (t->terms,
     492             :                              t->terms_size);
     493           0 :         if (TALER_MHD_body_compress (&buf,
     494             :                                      &bsize))
     495             :         {
     496           0 :           t->compressed_terms = buf;
     497           0 :           t->compressed_terms_size = bsize;
     498             :         }
     499             :         else
     500             :         {
     501           0 :           GNUNET_free (buf);
     502             :         }
     503             :         {
     504           0 :           struct Terms *prev = NULL;
     505             : 
     506           0 :           for (struct Terms *pos = legal->terms_head;
     507             :                NULL != pos;
     508           0 :                pos = pos->next)
     509             :           {
     510           0 :             if (pos->priority < priority)
     511           0 :               break;
     512           0 :             prev = pos;
     513             :           }
     514           0 :           GNUNET_CONTAINER_DLL_insert_after (legal->terms_head,
     515             :                                              legal->terms_tail,
     516             :                                              prev,
     517             :                                              t);
     518             :         }
     519             :       }
     520             :     }
     521             :   }
     522             : }
     523             : 
     524             : 
     525             : /**
     526             :  * Load all the terms of service from @a path under language @a lang.
     527             :  *
     528             :  * @param[in,out] legal where to write the result
     529             :  * @param path where the terms are found
     530             :  * @param lang which language directory to crawl
     531             :  */
     532             : static void
     533           0 : load_language (struct TALER_MHD_Legal *legal,
     534             :                const char *path,
     535             :                const char *lang)
     536             : {
     537             :   char *dname;
     538             :   DIR *d;
     539             : 
     540           0 :   GNUNET_asprintf (&dname,
     541             :                    "%s/%s",
     542             :                    path,
     543             :                    lang);
     544           0 :   d = opendir (dname);
     545           0 :   if (NULL == d)
     546             :   {
     547           0 :     GNUNET_free (dname);
     548           0 :     return;
     549             :   }
     550           0 :   for (struct dirent *de = readdir (d);
     551             :        NULL != de;
     552           0 :        de = readdir (d))
     553             :   {
     554           0 :     const char *fn = de->d_name;
     555             : 
     556           0 :     if (fn[0] == '.')
     557           0 :       continue;
     558           0 :     load_terms (legal, path, lang, fn);
     559             :   }
     560           0 :   GNUNET_break (0 == closedir (d));
     561           0 :   GNUNET_free (dname);
     562             : }
     563             : 
     564             : 
     565             : struct TALER_MHD_Legal *
     566           0 : TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg,
     567             :                       const char *section,
     568             :                       const char *diroption,
     569             :                       const char *tagoption)
     570             : {
     571             :   struct TALER_MHD_Legal *legal;
     572             :   char *path;
     573             :   DIR *d;
     574             : 
     575           0 :   legal = GNUNET_new (struct TALER_MHD_Legal);
     576           0 :   if (GNUNET_OK !=
     577           0 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     578             :                                              section,
     579             :                                              tagoption,
     580             :                                              &legal->terms_etag))
     581             :   {
     582           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     583             :                                section,
     584             :                                tagoption);
     585           0 :     GNUNET_free (legal);
     586           0 :     return NULL;
     587             :   }
     588           0 :   if (GNUNET_OK !=
     589           0 :       GNUNET_CONFIGURATION_get_value_filename (cfg,
     590             :                                                section,
     591             :                                                diroption,
     592             :                                                &path))
     593             :   {
     594           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     595             :                                section,
     596             :                                diroption);
     597           0 :     GNUNET_free (legal->terms_etag);
     598           0 :     GNUNET_free (legal);
     599           0 :     return NULL;
     600             :   }
     601           0 :   d = opendir (path);
     602           0 :   if (NULL == d)
     603             :   {
     604           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
     605             :                                section,
     606             :                                diroption,
     607             :                                "Could not open directory");
     608           0 :     GNUNET_free (legal->terms_etag);
     609           0 :     GNUNET_free (legal);
     610           0 :     GNUNET_free (path);
     611           0 :     return NULL;
     612             :   }
     613           0 :   for (struct dirent *de = readdir (d);
     614             :        NULL != de;
     615           0 :        de = readdir (d))
     616             :   {
     617           0 :     const char *lang = de->d_name;
     618             : 
     619           0 :     if (lang[0] == '.')
     620           0 :       continue;
     621           0 :     load_language (legal, path, lang);
     622             :   }
     623           0 :   GNUNET_break (0 == closedir (d));
     624           0 :   GNUNET_free (path);
     625           0 :   return legal;
     626             : }
     627             : 
     628             : 
     629             : void
     630           0 : TALER_MHD_legal_free (struct TALER_MHD_Legal *legal)
     631             : {
     632             :   struct Terms *t;
     633           0 :   if (NULL == legal)
     634           0 :     return;
     635           0 :   while (NULL != (t = legal->terms_head))
     636             :   {
     637           0 :     GNUNET_free (t->language);
     638           0 :     GNUNET_free (t->compressed_terms);
     639           0 :     if (0 != munmap (t->terms, t->terms_size))
     640           0 :       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     641             :                            "munmap");
     642           0 :     GNUNET_CONTAINER_DLL_remove (legal->terms_head,
     643             :                                  legal->terms_tail,
     644             :                                  t);
     645           0 :     GNUNET_free (t);
     646             :   }
     647           0 :   GNUNET_free (legal->terms_etag);
     648           0 :   GNUNET_free (legal);
     649             : }

Generated by: LCOV version 1.14