merchant

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

product_parse.c (9387B)


      1 /*
      2   This file is part of TALER
      3   (C) 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/product_parse.c
     18  * @brief small helpers
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <string.h>
     23 #include "taler/taler_merchant_util.h"
     24 #include <gnunet/gnunet_json_lib.h>
     25 #include <taler/taler_json_lib.h>
     26 
     27 
     28 /**
     29  * Maximum number of entries we accept in the @e prices array of a
     30  * product.  The array is allocated from its JSON length before any
     31  * entry is parsed, so the length must be bounded independently of the
     32  * maximum upload size.
     33  */
     34 #define MAX_PRICES 1024
     35 
     36 
     37 /**
     38  * Parse the given unit quantity string @a s and store the result in @a q.
     39  *
     40  * @param s quantity to parse
     41  * @param[out] q where to store the result
     42  * @return #GNUNET_OK on success
     43  */
     44 static enum GNUNET_GenericReturnValue
     45 parse_unit_quantity (const char *s,
     46                      struct TALER_MERCHANT_ProductQuantity *q)
     47 {
     48   const char *ptr;
     49   uint64_t integer = 0;
     50   uint32_t frac = 0;
     51   unsigned int digits = 0;
     52 
     53   if (NULL == s)
     54   {
     55     GNUNET_break_op (0);
     56     return GNUNET_SYSERR;
     57   }
     58   ptr = s;
     59   if ('\0' == *ptr)
     60   {
     61     GNUNET_break_op (0);
     62     return GNUNET_SYSERR;
     63   }
     64   if ('-' == *ptr)
     65   {
     66     GNUNET_break_op (0);
     67     return GNUNET_SYSERR;
     68   }
     69   if (! isdigit ((unsigned char) *ptr))
     70   {
     71     GNUNET_break_op (0);
     72     return GNUNET_SYSERR;
     73   }
     74   while (isdigit ((unsigned char) *ptr))
     75   {
     76     unsigned int digit = (unsigned int) (*ptr - '0');
     77 
     78     /* We intentionally allow at most INT64_MAX (as -1 has special meanings),
     79        even though the data type would support UINT64_MAX */
     80     if (integer > (INT64_MAX - digit) / 10)
     81     {
     82       GNUNET_break_op (0);
     83       return GNUNET_SYSERR;
     84     }
     85     integer = integer * 10 + digit;
     86     ptr++;
     87   }
     88   if ('.' == *ptr)
     89   {
     90     ptr++;
     91     if ('\0' == *ptr)
     92     {
     93       GNUNET_break_op (0);
     94       return GNUNET_SYSERR;
     95     }
     96     while (isdigit ((unsigned char) *ptr))
     97     {
     98       unsigned int digit = (unsigned int) (*ptr - '0');
     99 
    100       if (digits >= TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
    101       {
    102         GNUNET_break_op (0);
    103         return GNUNET_SYSERR;
    104       }
    105       frac = (uint32_t) (frac * 10 + digit);
    106       digits++;
    107       ptr++;
    108     }
    109     while (digits < TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
    110     {
    111       frac *= 10;
    112       digits++;
    113     }
    114   }
    115   if ('\0' != *ptr)
    116   {
    117     GNUNET_break_op (0);
    118     return GNUNET_SYSERR;
    119   }
    120   q->integer = integer;
    121   q->fractional = frac;
    122   return GNUNET_OK;
    123 }
    124 
    125 
    126 enum GNUNET_GenericReturnValue
    127 TALER_MERCHANT_parse_product_sold (const json_t *pj,
    128                                    struct TALER_MERCHANT_ProductSold *r,
    129                                    bool allow_mip)
    130 {
    131   // FIXME: properly implement distinction between MIP and non-MIP!
    132   bool no_quantity;
    133   bool no_unit_quantity;
    134   bool no_price;
    135   uint64_t legacy_quantity;
    136   const char *unit_quantity_s;
    137   struct TALER_Amount price;
    138   const json_t *prices = NULL;
    139   struct GNUNET_JSON_Specification spec[] = {
    140     GNUNET_JSON_spec_mark_optional (
    141       TALER_JSON_spec_slug_copy ("product_id",
    142                                  &r->product_id),
    143       NULL),
    144     GNUNET_JSON_spec_mark_optional (
    145       GNUNET_JSON_spec_string_copy ("product_name",
    146                                     &r->product_name),
    147       NULL),
    148     GNUNET_JSON_spec_mark_optional (
    149       GNUNET_JSON_spec_string_copy ("description",
    150                                     &r->description),
    151       NULL),
    152     GNUNET_JSON_spec_mark_optional (
    153       GNUNET_JSON_spec_object_copy ("description_i18n",
    154                                     &r->description_i18n),
    155       NULL),
    156     GNUNET_JSON_spec_mark_optional (
    157       GNUNET_JSON_spec_uint64 ("quantity",
    158                                &legacy_quantity),
    159       &no_quantity),
    160     GNUNET_JSON_spec_mark_optional (
    161       GNUNET_JSON_spec_bool ("prices_are_net",
    162                              &r->prices_are_net),
    163       NULL),
    164     GNUNET_JSON_spec_mark_optional (
    165       GNUNET_JSON_spec_string ("unit_quantity",
    166                                &unit_quantity_s),
    167       &no_unit_quantity),
    168     GNUNET_JSON_spec_mark_optional (
    169       TALER_JSON_spec_slug_copy ("unit",
    170                                  &r->unit),
    171       NULL),
    172     GNUNET_JSON_spec_mark_optional (
    173       TALER_JSON_spec_amount_any ("price",
    174                                   &price),
    175       &no_price),
    176     GNUNET_JSON_spec_mark_optional (
    177       GNUNET_JSON_spec_array_const ("prices",
    178                                     &prices),
    179       NULL),
    180     GNUNET_JSON_spec_mark_optional (
    181       GNUNET_JSON_spec_string_copy ("image",
    182                                     &r->image),
    183       NULL),
    184     GNUNET_JSON_spec_mark_optional (
    185       GNUNET_JSON_spec_array_copy ("taxes",
    186                                    &r->taxes),
    187       NULL),
    188     GNUNET_JSON_spec_mark_optional (
    189       GNUNET_JSON_spec_timestamp ("delivery_date",
    190                                   &r->delivery_date),
    191       NULL),
    192     GNUNET_JSON_spec_mark_optional (
    193       GNUNET_JSON_spec_uint64 ("product_money_pot",
    194                                &r->product_money_pot),
    195       NULL),
    196     GNUNET_JSON_spec_end ()
    197   };
    198   enum GNUNET_GenericReturnValue res;
    199   const char *ename;
    200   unsigned int eline;
    201 
    202   r->delivery_date = GNUNET_TIME_UNIT_FOREVER_TS;
    203   res = GNUNET_JSON_parse (pj,
    204                            spec,
    205                            &ename,
    206                            &eline);
    207   if (GNUNET_OK != res)
    208   {
    209     GNUNET_break (0);
    210     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    211                 "Failed to parse product at field %s\n",
    212                 ename);
    213     return GNUNET_SYSERR;
    214   }
    215   /* money pots are identified by their BIGINT serial */
    216   if (r->product_money_pot > (uint64_t) INT64_MAX)
    217   {
    218     GNUNET_break_op (0);
    219     return GNUNET_SYSERR;
    220   }
    221   if (! no_quantity)
    222   {
    223     /* same range as "unit_quantity", see parse_unit_quantity() */
    224     if (legacy_quantity > (uint64_t) INT64_MAX)
    225     {
    226       GNUNET_break_op (0);
    227       return GNUNET_SYSERR;
    228     }
    229     r->unit_quantity.integer = legacy_quantity;
    230     r->unit_quantity.fractional = 0;
    231   }
    232   if (! no_unit_quantity)
    233   {
    234     if (GNUNET_OK !=
    235         parse_unit_quantity (unit_quantity_s,
    236                              &r->unit_quantity))
    237     {
    238       GNUNET_break (0);
    239       return GNUNET_SYSERR;
    240     }
    241   }
    242   if ( (! no_quantity) && (! no_unit_quantity) )
    243   {
    244     GNUNET_break ( (0 == r->unit_quantity.fractional) &&
    245                    (legacy_quantity == r->unit_quantity.integer) );
    246   }
    247   if ( (NULL != r->image) &&
    248        (! TALER_MERCHANT_image_data_url_valid (r->image)) )
    249   {
    250     GNUNET_break_op (0);
    251     return GNUNET_SYSERR;
    252   }
    253   if ( (NULL != r->taxes) &&
    254        (! TALER_MERCHANT_taxes_array_valid (r->taxes)) )
    255   {
    256     GNUNET_break_op (0);
    257     return GNUNET_SYSERR;
    258   }
    259   if ( (NULL != prices) &&
    260        (0 != json_array_size (prices)) )
    261   {
    262     size_t len = json_array_size (prices);
    263     size_t i;
    264     json_t *price_i;
    265 
    266     if (MAX_PRICES < len)
    267     {
    268       /* 'prices' is untrusted network input; reject rather than abort
    269          in GNUNET_xmalloc_() (which asserts above 40 MiB).  (The
    270          zero-length case is excluded by the guard above, so we never
    271          call GNUNET_new_array(0, ...).) */
    272       GNUNET_break_op (0);
    273       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    274                   "Too many prices for product (%u, at most %u allowed)\n",
    275                   (unsigned int) len,
    276                   (unsigned int) MAX_PRICES);
    277       return GNUNET_SYSERR;
    278     }
    279     r->prices_length = (unsigned int) len;
    280     r->prices = GNUNET_new_array (r->prices_length,
    281                                   struct TALER_Amount);
    282     json_array_foreach (prices, i, price_i)
    283     {
    284       struct GNUNET_JSON_Specification pspec[] = {
    285         TALER_JSON_spec_amount_any (NULL,
    286                                     &r->prices[i]),
    287         GNUNET_JSON_spec_end ()
    288       };
    289 
    290       res = GNUNET_JSON_parse (price_i,
    291                                pspec,
    292                                &ename,
    293                                &eline);
    294       if (GNUNET_OK != res)
    295       {
    296         GNUNET_break (0);
    297         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    298                     "Failed to parse price at index %u\n",
    299                     (unsigned int) i);
    300         return GNUNET_SYSERR;
    301       }
    302     }
    303   }
    304   else if (! no_price)
    305   {
    306     r->prices_length = 1;
    307     r->prices = GNUNET_new_array (1,
    308                                   struct TALER_Amount);
    309     r->prices[0] = price;
    310   }
    311   return GNUNET_OK;
    312 }
    313 
    314 
    315 void
    316 TALER_MERCHANT_product_sold_free (struct TALER_MERCHANT_ProductSold *product)
    317 {
    318   GNUNET_free (product->product_id);
    319   GNUNET_free (product->product_name);
    320   GNUNET_free (product->description);
    321   json_decref (product->description_i18n);
    322   GNUNET_free (product->prices);
    323   GNUNET_free (product->unit);
    324   GNUNET_free (product->image);
    325   json_decref (product->taxes);
    326 }