LCOV - code coverage report
Current view: top level - util - template_parse.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 64.2 % 190 122
Test Date: 2026-09-11 17:45:24 Functions: 88.9 % 9 8

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2025 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Lesser 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 src/util/template_parse.c
      18              :  * @brief shared logic for template contract parsing
      19              :  * @author Bohdan Potuzhnyi
      20              :  */
      21              : #include "platform.h"
      22              : #include <gnunet/gnunet_common.h>
      23              : #include <gnunet/gnunet_json_lib.h>
      24              : #include <jansson.h>
      25              : #include <string.h>
      26              : #include <taler/taler_json_lib.h>
      27              : #include <taler/taler_util.h>
      28              : #include "taler/taler_merchant_util.h"
      29              : #include <regex.h>
      30              : 
      31              : 
      32              : enum TALER_MERCHANT_TemplateType
      33           51 : TALER_MERCHANT_template_type_from_contract (const json_t *template_contract)
      34              : {
      35              :   const json_t *type_val;
      36              : 
      37           51 :   if (NULL == template_contract)
      38            0 :     return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
      39              : 
      40           51 :   type_val = json_object_get (template_contract,
      41              :                               "template_type");
      42              : 
      43           51 :   if (NULL == type_val)
      44              :   {
      45              :     /* missing => fixed order */
      46           18 :     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
      47              :   }
      48           33 :   if (! json_is_string (type_val))
      49              :   {
      50            0 :     GNUNET_break_op (0);
      51            0 :     return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
      52              :   }
      53              : 
      54           33 :   return TALER_MERCHANT_template_type_from_string (
      55              :     json_string_value (type_val));
      56              : }
      57              : 
      58              : 
      59              : enum TALER_MERCHANT_TemplateType
      60          165 : TALER_MERCHANT_template_type_from_string (const char *template_type)
      61              : {
      62          165 :   if (NULL == template_type)
      63           49 :     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
      64          116 :   if (0 == strcmp (template_type,
      65              :                    "fixed-order"))
      66           18 :     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
      67           98 :   if (0 == strcmp (template_type,
      68              :                    "inventory-cart"))
      69           72 :     return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
      70           26 :   if (0 == strcmp (template_type,
      71              :                    "paivana"))
      72           26 :     return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
      73            0 :   return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
      74              : }
      75              : 
      76              : 
      77              : const char *
      78            0 : TALER_MERCHANT_template_type_to_string (
      79              :   enum TALER_MERCHANT_TemplateType template_type)
      80              : {
      81            0 :   switch (template_type)
      82              :   {
      83            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
      84            0 :     return "fixed-order";
      85            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
      86            0 :     return "inventory-cart";
      87            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
      88            0 :     return "paivana";
      89            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
      90            0 :     break;
      91              :   }
      92            0 :   return NULL;
      93              : }
      94              : 
      95              : 
      96              : /**
      97              :  * Parse inventory-specific fields from a template contract.
      98              :  *
      99              :  * @param template_contract json
     100              :  * @param[out] out where to write parsed fields
     101              :  * @param[out] error_name error description
     102              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure
     103              :  */
     104              : static enum GNUNET_GenericReturnValue
     105           28 : parse_template_inventory (const json_t *template_contract,
     106              :                           struct TALER_MERCHANT_TemplateContract *out,
     107              :                           const char **error_name)
     108              : {
     109              :   struct GNUNET_JSON_Specification spec[] = {
     110           28 :     GNUNET_JSON_spec_mark_optional (
     111              :       GNUNET_JSON_spec_bool (
     112              :         "selected_all",
     113              :         &out->details.inventory.selected_all),
     114              :       NULL),
     115           28 :     GNUNET_JSON_spec_mark_optional (
     116              :       GNUNET_JSON_spec_array_const (
     117              :         "selected_categories",
     118              :         &out->details.inventory.selected_categories)
     119              :       ,
     120              :       NULL),
     121           28 :     GNUNET_JSON_spec_mark_optional (
     122              :       GNUNET_JSON_spec_array_const (
     123              :         "selected_products",
     124              :         &out->details.inventory.selected_products),
     125              :       NULL),
     126           28 :     GNUNET_JSON_spec_mark_optional (
     127              :       GNUNET_JSON_spec_bool (
     128              :         "choose_one",
     129              :         &out->details.inventory.choose_one),
     130              :       NULL),
     131           28 :     GNUNET_JSON_spec_end ()
     132              :   };
     133              :   const char *en;
     134              : 
     135           28 :   if (GNUNET_OK !=
     136           28 :       GNUNET_JSON_parse ((json_t *) template_contract,
     137              :                          spec,
     138              :                          &en,
     139              :                          NULL))
     140              :   {
     141            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     142              :                 "Invalid inventory template_contract for field %s\n",
     143              :                 en);
     144            0 :     if (NULL != error_name)
     145            0 :       *error_name = en;
     146            0 :     return GNUNET_SYSERR;
     147              :   }
     148              : 
     149           28 :   if (NULL != out->details.inventory.selected_categories)
     150              :   {
     151              :     const json_t *entry;
     152              :     size_t idx;
     153              : 
     154            8 :     json_array_foreach ((json_t *) out->details.inventory.selected_categories,
     155              :                         idx,
     156              :                         entry)
     157              :     {
     158            8 :       if ( (! json_is_integer (entry)) ||
     159            4 :            (0 > json_integer_value (entry)) )
     160              :       {
     161            0 :         GNUNET_break_op (0);
     162            0 :         if (NULL != error_name)
     163            0 :           *error_name = "selected_categories";
     164            0 :         return GNUNET_SYSERR;
     165              :       }
     166              :     }
     167              :   }
     168              : 
     169           28 :   if (NULL != out->details.inventory.selected_products)
     170              :   {
     171              :     const json_t *entry;
     172              :     size_t idx;
     173              : 
     174           80 :     json_array_foreach ((json_t *) out->details.inventory.selected_products,
     175              :                         idx,
     176              :                         entry)
     177              :     {
     178           52 :       if (! json_is_string (entry))
     179              :       {
     180            0 :         GNUNET_break_op (0);
     181            0 :         if (NULL != error_name)
     182            0 :           *error_name = "selected_products";
     183            0 :         return GNUNET_SYSERR;
     184              :       }
     185              :     }
     186              :   }
     187           28 :   return GNUNET_OK;
     188              : }
     189              : 
     190              : 
     191              : /**
     192              :  * Parse paivana-specific fields from a template contract.
     193              :  *
     194              :  * @param template_contract json
     195              :  * @param[out] out where to write parsed fields
     196              :  * @param[out] error_name error description
     197              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure
     198              :  */
     199              : static enum GNUNET_GenericReturnValue
     200            9 : parse_template_paivana (const json_t *template_contract,
     201              :                         struct TALER_MERCHANT_TemplateContract *out,
     202              :                         const char **error_name)
     203              : {
     204              :   struct GNUNET_JSON_Specification spec[] = {
     205            9 :     GNUNET_JSON_spec_mark_optional (
     206              :       GNUNET_JSON_spec_string ("website_regex",
     207              :                                &out->details.paivana.website_regex),
     208              :       NULL),
     209            9 :     TALER_MERCHANT_spec_order_choices ("choices",
     210              :                                        &out->details.paivana.choices,
     211              :                                        &out->details.paivana.choices_len),
     212            9 :     GNUNET_JSON_spec_end ()
     213              :   };
     214              :   const char *en;
     215              : 
     216            9 :   if (GNUNET_OK !=
     217            9 :       GNUNET_JSON_parse ((json_t *) template_contract,
     218              :                          spec,
     219              :                          &en,
     220              :                          NULL))
     221              :   {
     222            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     223              :                 "Invalid paivana template_contract for field %s\n",
     224              :                 en);
     225            0 :     if (NULL != error_name)
     226            0 :       *error_name = en;
     227            0 :     return GNUNET_SYSERR;
     228              :   }
     229            9 :   if (NULL != out->details.paivana.website_regex)
     230              :   {
     231              :     regex_t ex;
     232              : 
     233            0 :     if (0 != regcomp (&ex,
     234              :                       out->details.paivana.website_regex,
     235              :                       REG_NOSUB | REG_EXTENDED))
     236              :     {
     237            0 :       GNUNET_break_op (0);
     238            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     239              :                   "Invalid paivana website_regex given\n");
     240            0 :       if (NULL != error_name)
     241            0 :         *error_name = "Invalid website_regex given";
     242            0 :       return GNUNET_SYSERR;
     243              :     }
     244            0 :     regfree (&ex);
     245              :   }
     246            9 :   return GNUNET_OK;
     247              : }
     248              : 
     249              : 
     250              : /**
     251              :  * Check that the (optional) @e min_amount and @e max_amount of @a tc are
     252              :  * usable: they must be denominated in the same currency, which must be
     253              :  * the currency of the template (which then must be given, as otherwise we
     254              :  * could not tell in which currency to enforce the limits), and they must
     255              :  * not exclude each other.
     256              :  *
     257              :  * @param tc template contract to check
     258              :  * @param[out] error_name error description
     259              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on validation failure
     260              :  */
     261              : static enum GNUNET_GenericReturnValue
     262           89 : check_amount_limits (const struct TALER_MERCHANT_TemplateContract *tc,
     263              :                      const char **error_name)
     264              : {
     265           89 :   if (tc->no_min_amount &&
     266           80 :       tc->no_max_amount)
     267           80 :     return GNUNET_OK;
     268            9 :   if ( (! tc->no_min_amount) &&
     269            9 :        (! tc->no_max_amount) )
     270              :   {
     271              :     /* Must be checked before the two are compared, as comparing
     272              :        amounts of different currencies fails an assertion. Note that
     273              :        we deliberately do not rely on both having been checked against
     274              :        the currency of the template here. */
     275            8 :     if (GNUNET_YES !=
     276            8 :         TALER_amount_cmp_currency (&tc->min_amount,
     277              :                                    &tc->max_amount))
     278              :     {
     279            1 :       GNUNET_break_op (0);
     280            1 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     281              :                   "min_amount and max_amount must use the same currency\n");
     282            1 :       if (NULL != error_name)
     283            0 :         *error_name = "max_amount";
     284            1 :       return GNUNET_SYSERR;
     285              :     }
     286            7 :     if (0 < TALER_amount_cmp (&tc->min_amount,
     287              :                               &tc->max_amount))
     288              :     {
     289            1 :       GNUNET_break_op (0);
     290            1 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     291              :                   "min_amount exceeds max_amount\n");
     292            1 :       if (NULL != error_name)
     293            0 :         *error_name = "min_amount";
     294            1 :       return GNUNET_SYSERR;
     295              :     }
     296              :   }
     297            7 :   if (NULL == tc->currency)
     298              :   {
     299            1 :     GNUNET_break_op (0);
     300            1 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     301              :                 "Template with min_amount or max_amount must specify a currency\n");
     302            1 :     if (NULL != error_name)
     303            0 :       *error_name = "currency";
     304            1 :     return GNUNET_SYSERR;
     305              :   }
     306            6 :   if ( (! tc->no_min_amount) &&
     307            6 :        (0 != strcasecmp (tc->currency,
     308            6 :                          tc->min_amount.currency)) )
     309              :   {
     310            0 :     GNUNET_break_op (0);
     311            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     312              :                 "min_amount must use the currency of the template (%s)\n",
     313              :                 tc->currency);
     314            0 :     if (NULL != error_name)
     315            0 :       *error_name = "min_amount";
     316            0 :     return GNUNET_SYSERR;
     317              :   }
     318            6 :   if ( (! tc->no_max_amount) &&
     319            6 :        (0 != strcasecmp (tc->currency,
     320            6 :                          tc->max_amount.currency)) )
     321              :   {
     322            0 :     GNUNET_break_op (0);
     323            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     324              :                 "max_amount must use the currency of the template (%s)\n",
     325              :                 tc->currency);
     326            0 :     if (NULL != error_name)
     327            0 :       *error_name = "max_amount";
     328            0 :     return GNUNET_SYSERR;
     329              :   }
     330            6 :   return GNUNET_OK;
     331              : }
     332              : 
     333              : 
     334              : enum GNUNET_GenericReturnValue
     335           89 : TALER_MERCHANT_template_contract_parse (
     336              :   const json_t *template_contract,
     337              :   struct TALER_MERCHANT_TemplateContract *out,
     338              :   const char **error_name)
     339              : {
     340           89 :   const char *template_type_str = NULL;
     341              :   struct GNUNET_JSON_Specification spec[] = {
     342           89 :     GNUNET_JSON_spec_mark_optional (
     343              :       GNUNET_JSON_spec_string ("template_type",
     344              :                                &template_type_str),
     345              :       NULL),
     346           89 :     GNUNET_JSON_spec_mark_optional (
     347              :       GNUNET_JSON_spec_string ("summary",
     348              :                                &out->summary),
     349              :       NULL),
     350           89 :     GNUNET_JSON_spec_mark_optional (
     351              :       GNUNET_JSON_spec_string ("currency",
     352              :                                &out->currency),
     353              :       NULL),
     354           89 :     GNUNET_JSON_spec_mark_optional (
     355              :       TALER_JSON_spec_amount_any ("amount",
     356              :                                   &out->amount),
     357              :       &out->no_amount),
     358           89 :     GNUNET_JSON_spec_mark_optional (
     359              :       TALER_JSON_spec_amount_any ("min_amount",
     360              :                                   &out->min_amount),
     361              :       &out->no_min_amount),
     362           89 :     GNUNET_JSON_spec_mark_optional (
     363              :       TALER_JSON_spec_amount_any ("max_amount",
     364              :                                   &out->max_amount),
     365              :       &out->no_max_amount),
     366           89 :     GNUNET_JSON_spec_mark_optional (
     367              :       GNUNET_JSON_spec_uint32 ("minimum_age",
     368              :                                &out->minimum_age),
     369              :       NULL),
     370           89 :     GNUNET_JSON_spec_mark_optional (
     371              :       GNUNET_JSON_spec_relative_time ("pay_duration",
     372              :                                       &out->pay_duration),
     373              :       NULL),
     374           89 :     GNUNET_JSON_spec_mark_optional (
     375              :       GNUNET_JSON_spec_relative_time ("max_pickup_duration",
     376              :                                       &out->max_pickup_duration),
     377              :       NULL),
     378           89 :     GNUNET_JSON_spec_mark_optional (
     379              :       GNUNET_JSON_spec_bool ("request_tip",
     380              :                              &out->request_tip),
     381              :       NULL),
     382           89 :     GNUNET_JSON_spec_end ()
     383              :   };
     384              :   const char *en;
     385              : 
     386           89 :   if (NULL == template_contract)
     387              :   {
     388            0 :     if (NULL != error_name)
     389            0 :       *error_name = "template_contract is NULL";
     390            0 :     return GNUNET_SYSERR;
     391              :   }
     392           89 :   out->max_pickup_duration = GNUNET_TIME_UNIT_FOREVER_REL;
     393           89 :   out->no_min_amount = true;
     394           89 :   out->no_max_amount = true;
     395           89 :   if (GNUNET_OK !=
     396           89 :       GNUNET_JSON_parse ((json_t *) template_contract,
     397              :                          spec,
     398              :                          &en,
     399              :                          NULL))
     400              :   {
     401            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     402              :                 "Invalid input for field %s\n",
     403              :                 en);
     404            0 :     if (NULL != error_name)
     405            0 :       *error_name = en;
     406            0 :     return GNUNET_SYSERR;
     407              :   }
     408           89 :   if (GNUNET_OK !=
     409           89 :       check_amount_limits (out,
     410              :                            error_name))
     411            3 :     return GNUNET_SYSERR;
     412              : 
     413           86 :   out->type = TALER_MERCHANT_template_type_from_string (template_type_str);
     414           86 :   if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type)
     415              :   {
     416            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     417              :                 "Invalid template_type used '%s'\n",
     418              :                 template_type_str);
     419            0 :     if (NULL != error_name)
     420            0 :       *error_name = "Invalid template_type used";
     421            0 :     return GNUNET_SYSERR;
     422              :   }
     423              : 
     424              :   /* Parse additional fields for each specific type */
     425           86 :   switch (out->type)
     426              :   {
     427           49 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
     428           49 :     return GNUNET_OK;
     429           28 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
     430           28 :     return parse_template_inventory (template_contract,
     431              :                                      out,
     432              :                                      error_name);
     433            9 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
     434            9 :     return parse_template_paivana (template_contract,
     435              :                                    out,
     436              :                                    error_name);
     437            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
     438            0 :     break;
     439              :   }
     440              : 
     441              :   /* I think we are never supposed to reach it */
     442            0 :   GNUNET_break_op (0);
     443            0 :   if (NULL != error_name)
     444            0 :     *error_name = "template_type";
     445            0 :   return GNUNET_SYSERR;
     446              : }
     447              : 
     448              : 
     449              : void
     450           92 : TALER_MERCHANT_template_contract_free (
     451              :   struct TALER_MERCHANT_TemplateContract *tc)
     452              : {
     453           92 :   switch (tc->type)
     454              :   {
     455           49 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
     456           49 :     return;
     457           28 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
     458           28 :     return;
     459            9 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
     460           18 :     for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++)
     461            9 :       TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]);
     462            9 :     GNUNET_array_grow (tc->details.paivana.choices,
     463              :                        tc->details.paivana.choices_len,
     464              :                        0);
     465            9 :     return;
     466            6 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
     467            6 :     return;
     468              :   }
     469              : }
     470              : 
     471              : 
     472              : bool
     473           45 : TALER_MERCHANT_template_contract_valid (const json_t *template_contract)
     474              : {
     475              :   struct TALER_MERCHANT_TemplateContract tmp;
     476              :   bool ret;
     477              : 
     478           45 :   memset (&tmp,
     479              :           0,
     480              :           sizeof (tmp));
     481           45 :   ret = (GNUNET_OK ==
     482           45 :          TALER_MERCHANT_template_contract_parse (template_contract,
     483              :                                                  &tmp,
     484              :                                                  NULL));
     485           87 :   if (ret &&
     486           42 :       GNUNET_TIME_relative_is_forever (tmp.pay_duration))
     487            5 :     ret = false;
     488           45 :   TALER_MERCHANT_template_contract_free (&tmp);
     489           45 :   return ret;
     490              : }
        

Generated by: LCOV version 2.0-1