merchant

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

contract_choice_parse.c (14630B)


      1 /*
      2   This file is part of TALER
      3   (C) 2024, 2025, 2026 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/contract_choice_parse.c
     18  * @brief shared logic for contract choice parsing
     19  * @author Iván Ávalos
     20  * @author Christian Grothoff
     21  */
     22 #include "platform.h"
     23 #include <gnunet/gnunet_common.h>
     24 #include <gnunet/gnunet_json_lib.h>
     25 #include <jansson.h>
     26 #include <stdbool.h>
     27 #include <stdint.h>
     28 #include <taler/taler_json_lib.h>
     29 #include <taler/taler_util.h>
     30 #include "taler/taler_merchant_util.h"
     31 
     32 
     33 /**
     34  * Free contract choice output details in @a output, but not @a output itself
     35  *
     36  * @param[in,out] output contract output details to clean up
     37  */
     38 static void
     39 contract_choice_output_free (
     40   struct TALER_MERCHANT_ContractOutput *output)
     41 {
     42 
     43   switch (output->type)
     44   {
     45   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
     46     GNUNET_break (0);
     47     break;
     48   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
     49     break;
     50   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
     51     for (unsigned int j = 0;
     52          j<output->details.donation_receipt.donau_urls_len;
     53          j++)
     54       GNUNET_free (output->details.donation_receipt.donau_urls[j]);
     55     GNUNET_array_grow (output->details.donation_receipt.donau_urls,
     56                        output->details.donation_receipt.donau_urls_len,
     57                        0);
     58     break;
     59 #if FUTURE
     60   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN:
     61     GNUNET_free (output->details.coin.exchange_url);
     62     break;
     63 #endif
     64   }
     65 }
     66 
     67 
     68 /**
     69  * Parse JSON contract terms choice input.
     70  *
     71  * @param[in] root JSON object containing choice input
     72  * @param[out] input parsed choice input, NULL if @a input is malformed
     73  * @param index index of choice input in inputs array
     74  * @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise
     75  */
     76 static enum GNUNET_GenericReturnValue
     77 parse_contract_choice_input (
     78   json_t *root,
     79   struct TALER_MERCHANT_ContractInput *input,
     80   size_t index)
     81 {
     82   const char *ename;
     83   unsigned int eline;
     84   struct GNUNET_JSON_Specification ispec[] = {
     85     TALER_MERCHANT_json_spec_cit ("type",
     86                                   &input->type),
     87     GNUNET_JSON_spec_end ()
     88   };
     89 
     90   if (GNUNET_OK !=
     91       GNUNET_JSON_parse (root,
     92                          ispec,
     93                          &ename,
     94                          &eline))
     95   {
     96     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     97                 "Failed to parse %s at %u: %s\n",
     98                 ispec[eline].field,
     99                 eline,
    100                 ename);
    101     GNUNET_break_op (0);
    102     return GNUNET_SYSERR;
    103   }
    104 
    105   switch (input->type)
    106   {
    107   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
    108     GNUNET_break (0);
    109     break;
    110   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
    111     {
    112       struct GNUNET_JSON_Specification spec[] = {
    113         GNUNET_JSON_spec_string ("token_family_slug",
    114                                  &input->details.token.token_family_slug),
    115         GNUNET_JSON_spec_mark_optional (
    116           GNUNET_JSON_spec_uint ("count",
    117                                  &input->details.token.count),
    118           NULL),
    119         GNUNET_JSON_spec_end ()
    120       };
    121 
    122       if (GNUNET_OK !=
    123           GNUNET_JSON_parse (root,
    124                              spec,
    125                              &ename,
    126                              &eline))
    127       {
    128         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    129                     "Failed to parse %s at %u: %s\n",
    130                     spec[eline].field,
    131                     eline,
    132                     ename);
    133         GNUNET_break_op (0);
    134         return GNUNET_SYSERR;
    135       }
    136 
    137       return GNUNET_OK;
    138     }
    139   }
    140 
    141   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    142               "Field 'type' invalid in input #%u\n",
    143               (unsigned int) index);
    144   GNUNET_break_op (0);
    145   return GNUNET_SYSERR;
    146 }
    147 
    148 
    149 /**
    150  * Parse JSON contract terms choice output.
    151  *
    152  * @param[in] root JSON object containing choice output
    153  * @param[out] output parsed choice output, NULL if @a output is malformed
    154  * @param index index of choice output in outputs array
    155  * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise
    156  */
    157 static enum GNUNET_GenericReturnValue
    158 parse_contract_choice_output (
    159   json_t *root,
    160   struct TALER_MERCHANT_ContractOutput *output,
    161   size_t index)
    162 {
    163   const char *ename;
    164   unsigned int eline;
    165   struct GNUNET_JSON_Specification ispec[] = {
    166     TALER_MERCHANT_json_spec_cot ("type",
    167                                   &output->type),
    168     GNUNET_JSON_spec_end ()
    169   };
    170 
    171   if (GNUNET_OK !=
    172       GNUNET_JSON_parse (root,
    173                          ispec,
    174                          &ename,
    175                          &eline))
    176   {
    177     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    178                 "Failed to parse %s at %u: %s\n",
    179                 ispec[eline].field,
    180                 eline,
    181                 ename);
    182     GNUNET_break_op (0);
    183     return GNUNET_SYSERR;
    184   }
    185 
    186   switch (output->type)
    187   {
    188   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    189     GNUNET_break (0);
    190     break;
    191   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    192     {
    193       struct GNUNET_JSON_Specification spec[] = {
    194         GNUNET_JSON_spec_string ("token_family_slug",
    195                                  &output->details.token.token_family_slug),
    196         GNUNET_JSON_spec_mark_optional (
    197           GNUNET_JSON_spec_uint ("count",
    198                                  &output->details.token.count),
    199           NULL),
    200         GNUNET_JSON_spec_mark_optional (
    201           GNUNET_JSON_spec_timestamp ("valid_at",
    202                                       &output->details.token.valid_at),
    203           NULL),
    204         GNUNET_JSON_spec_uint ("key_index",
    205                                &output->details.token.key_index),
    206         GNUNET_JSON_spec_end ()
    207       };
    208 
    209       if (GNUNET_OK !=
    210           GNUNET_JSON_parse (root,
    211                              spec,
    212                              &ename,
    213                              &eline))
    214       {
    215         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    216                     "Failed to parse %s at %u: %s\n",
    217                     spec[eline].field,
    218                     eline,
    219                     ename);
    220         GNUNET_break_op (0);
    221         return GNUNET_SYSERR;
    222       }
    223 
    224       return GNUNET_OK;
    225     }
    226   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    227     {
    228       const json_t *donau_urls = NULL;
    229       struct GNUNET_JSON_Specification spec[] = {
    230         TALER_JSON_spec_amount_any (
    231           "amount",
    232           &output->details.donation_receipt.amount),
    233         GNUNET_JSON_spec_array_const ("donau_urls",
    234                                       &donau_urls),
    235         GNUNET_JSON_spec_end ()
    236       };
    237 
    238       if (GNUNET_OK !=
    239           GNUNET_JSON_parse (root,
    240                              spec,
    241                              &ename,
    242                              &eline))
    243       {
    244         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    245                     "Failed to parse %s at %u: %s\n",
    246                     spec[eline].field,
    247                     eline,
    248                     ename);
    249         GNUNET_break_op (0);
    250         return GNUNET_SYSERR;
    251       }
    252 
    253       GNUNET_array_grow (output->details.donation_receipt.donau_urls,
    254                          output->details.donation_receipt.donau_urls_len,
    255                          json_array_size (donau_urls));
    256 
    257       for (unsigned int i = 0;
    258            i < output->details.donation_receipt.donau_urls_len;
    259            i++)
    260       {
    261         const json_t *jurl;
    262 
    263         jurl = json_array_get (donau_urls,
    264                                i);
    265         if (! json_is_string (jurl))
    266         {
    267           GNUNET_break_op (0);
    268           return GNUNET_SYSERR;
    269         }
    270         output->details.donation_receipt.donau_urls[i] =
    271           GNUNET_strdup (json_string_value (jurl));
    272       }
    273 
    274       return GNUNET_OK;
    275     }
    276   }
    277 
    278   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    279               "Field 'type' invalid in output #%u\n",
    280               (unsigned int) index);
    281   GNUNET_break_op (0);
    282   return GNUNET_SYSERR;
    283 }
    284 
    285 
    286 /**
    287  * Parse given JSON object to choices array.
    288  *
    289  * @param cls closure, pointer to array length
    290  * @param root the json array representing the choices
    291  * @param[out] ospec where to write the data
    292  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
    293  */
    294 static enum GNUNET_GenericReturnValue
    295 parse_contract_choices (
    296   void *cls,
    297   json_t *root,
    298   struct GNUNET_JSON_Specification *ospec)
    299 {
    300   struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr;
    301   unsigned int *choices_len = cls;
    302 
    303   if (! json_is_array (root))
    304   {
    305     GNUNET_break_op (0);
    306     return GNUNET_SYSERR;
    307   }
    308   if (0 == json_array_size (root))
    309   {
    310     /* empty list of choices is not allowed */
    311     GNUNET_break_op (0);
    312     return GNUNET_SYSERR;
    313   }
    314   *choices = NULL;
    315   *choices_len = 0;
    316   GNUNET_array_grow (*choices,
    317                      *choices_len,
    318                      json_array_size (root));
    319 
    320   for (unsigned int i = 0; i < *choices_len; i++)
    321   {
    322     struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i];
    323     const json_t *jinputs = NULL;
    324     const json_t *joutputs = NULL;
    325     struct GNUNET_JSON_Specification spec[] = {
    326       TALER_JSON_spec_amount_any ("amount",
    327                                   &choice->amount),
    328       GNUNET_JSON_spec_mark_optional (
    329         TALER_JSON_spec_amount_any ("tip",
    330                                     &choice->tip),
    331         &choice->no_tip),
    332       GNUNET_JSON_spec_mark_optional (
    333         GNUNET_JSON_spec_string_copy ("description",
    334                                       &choice->description),
    335         NULL),
    336       GNUNET_JSON_spec_mark_optional (
    337         GNUNET_JSON_spec_object_copy ("description_i18n",
    338                                       &choice->description_i18n),
    339         NULL),
    340       TALER_JSON_spec_amount_any ("max_fee",
    341                                   &choice->max_fee),
    342       GNUNET_JSON_spec_mark_optional (
    343         GNUNET_JSON_spec_array_const ("inputs",
    344                                       &jinputs),
    345         NULL),
    346       GNUNET_JSON_spec_mark_optional (
    347         GNUNET_JSON_spec_array_const ("outputs",
    348                                       &joutputs),
    349         NULL),
    350       GNUNET_JSON_spec_end ()
    351     };
    352     const char *ename;
    353     unsigned int eline;
    354 
    355     if (GNUNET_OK !=
    356         GNUNET_JSON_parse (json_array_get (root, i),
    357                            spec,
    358                            &ename,
    359                            &eline))
    360     {
    361       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    362                   "Failed to parse %s at %u: %s\n",
    363                   spec[eline].field,
    364                   eline,
    365                   ename);
    366       GNUNET_break_op (0);
    367       return GNUNET_SYSERR;
    368     }
    369     if ( (! choice->no_tip) &&
    370          (GNUNET_OK !=
    371           TALER_amount_cmp_currency (&choice->amount,
    372                                      &choice->tip)) )
    373     {
    374       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    375                   "Tip currency does not match amount currency in choice #%u\n",
    376                   i);
    377       GNUNET_break_op (0);
    378       return GNUNET_SYSERR;
    379     }
    380 
    381     if (NULL != jinputs)
    382     {
    383       const json_t *jinput;
    384       size_t idx;
    385 
    386       json_array_foreach ((json_t *) jinputs, idx, jinput)
    387       {
    388         struct TALER_MERCHANT_ContractInput input = {
    389           .details.token.count = 1
    390         };
    391 
    392         if (GNUNET_OK !=
    393             parse_contract_choice_input ((json_t *) jinput,
    394                                          &input,
    395                                          idx))
    396         {
    397           GNUNET_break (0);
    398           return GNUNET_SYSERR;
    399         }
    400         switch (input.type)
    401         {
    402         case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
    403           GNUNET_break_op (0);
    404           return GNUNET_SYSERR;
    405         case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
    406           /* Ignore inputs tokens with 'count' field set to 0 */
    407           if (0 == input.details.token.count)
    408             continue;
    409           break;
    410         }
    411         GNUNET_array_append (choice->inputs,
    412                              choice->inputs_len,
    413                              input);
    414       }
    415     }
    416 
    417     if (NULL != joutputs)
    418     {
    419       const json_t *joutput;
    420       size_t idx;
    421       json_array_foreach ((json_t *) joutputs, idx, joutput)
    422       {
    423         struct TALER_MERCHANT_ContractOutput output = {
    424           .details.token.count = 1
    425         };
    426 
    427         if (GNUNET_OK !=
    428             parse_contract_choice_output ((json_t *) joutput,
    429                                           &output,
    430                                           idx))
    431         {
    432           GNUNET_break (0);
    433           contract_choice_output_free (&output);
    434           return GNUNET_SYSERR;
    435         }
    436         switch (output.type)
    437         {
    438         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    439           GNUNET_break_op (0);
    440           contract_choice_output_free (&output);
    441           return GNUNET_SYSERR;
    442         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    443           /* Ignore output tokens with 'count' field set to 0 */
    444           if (0 == output.details.token.count)
    445           {
    446             contract_choice_output_free (&output);
    447             continue;
    448           }
    449           break;
    450         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    451           break;
    452         }
    453         GNUNET_array_append (choice->outputs,
    454                              choice->outputs_len,
    455                              output);
    456       }
    457     }
    458   }
    459 
    460   return GNUNET_OK;
    461 }
    462 
    463 
    464 struct GNUNET_JSON_Specification
    465 TALER_MERCHANT_spec_contract_choices (
    466   const char *name,
    467   struct TALER_MERCHANT_ContractChoice **choices,
    468   unsigned int *choices_len)
    469 {
    470   struct GNUNET_JSON_Specification ret = {
    471     .cls = (void *) choices_len,
    472     .parser = &parse_contract_choices,
    473     .field = name,
    474     .ptr = choices,
    475   };
    476 
    477   return ret;
    478 }
    479 
    480 
    481 void
    482 TALER_MERCHANT_contract_choice_free (
    483   struct TALER_MERCHANT_ContractChoice *choice)
    484 {
    485   for (unsigned int i = 0; i < choice->outputs_len; i++)
    486   {
    487     contract_choice_output_free (&choice->outputs[i]);
    488   }
    489   GNUNET_free (choice->description);
    490   if (NULL != choice->description_i18n)
    491   {
    492     json_decref (choice->description_i18n);
    493     choice->description_i18n = NULL;
    494   }
    495   GNUNET_free (choice->inputs);
    496   GNUNET_free (choice->outputs);
    497 }