product_parse.c (8228B)
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 * Parse the given unit quantity string @a s and store the result in @a q. 29 * 30 * @param s quantity to parse 31 * @param[out] q where to store the result 32 * @return #GNUNET_OK on success 33 */ 34 static enum GNUNET_GenericReturnValue 35 parse_unit_quantity (const char *s, 36 struct TALER_MERCHANT_ProductQuantity *q) 37 { 38 const char *ptr; 39 uint64_t integer = 0; 40 uint32_t frac = 0; 41 unsigned int digits = 0; 42 43 if (NULL == s) 44 { 45 GNUNET_break_op (0); 46 return GNUNET_SYSERR; 47 } 48 ptr = s; 49 if ('\0' == *ptr) 50 { 51 GNUNET_break_op (0); 52 return GNUNET_SYSERR; 53 } 54 if ('-' == *ptr) 55 { 56 GNUNET_break_op (0); 57 return GNUNET_SYSERR; 58 } 59 if (! isdigit ((unsigned char) *ptr)) 60 { 61 GNUNET_break_op (0); 62 return GNUNET_SYSERR; 63 } 64 while (isdigit ((unsigned char) *ptr)) 65 { 66 unsigned int digit = (unsigned int) (*ptr - '0'); 67 68 /* We intentionally allow at most INT64_MAX (as -1 has special meanings), 69 even though the data type would support UINT64_MAX */ 70 if (integer > (INT64_MAX - digit) / 10) 71 { 72 GNUNET_break_op (0); 73 return GNUNET_SYSERR; 74 } 75 integer = integer * 10 + digit; 76 ptr++; 77 } 78 if ('.' == *ptr) 79 { 80 ptr++; 81 if ('\0' == *ptr) 82 { 83 GNUNET_break_op (0); 84 return GNUNET_SYSERR; 85 } 86 while (isdigit ((unsigned char) *ptr)) 87 { 88 unsigned int digit = (unsigned int) (*ptr - '0'); 89 90 if (digits >= TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS) 91 { 92 GNUNET_break_op (0); 93 return GNUNET_SYSERR; 94 } 95 frac = (uint32_t) (frac * 10 + digit); 96 digits++; 97 ptr++; 98 } 99 while (digits < TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS) 100 { 101 frac *= 10; 102 digits++; 103 } 104 } 105 if ('\0' != *ptr) 106 { 107 GNUNET_break_op (0); 108 return GNUNET_SYSERR; 109 } 110 q->integer = integer; 111 q->fractional = frac; 112 return GNUNET_OK; 113 } 114 115 116 enum GNUNET_GenericReturnValue 117 TALER_MERCHANT_parse_product_sold (const json_t *pj, 118 struct TALER_MERCHANT_ProductSold *r, 119 bool allow_mip) 120 { 121 // FIXME: properly implement distinction between MIP and non-MIP! 122 bool no_quantity; 123 bool no_unit_quantity; 124 bool no_price; 125 uint64_t legacy_quantity; 126 const char *unit_quantity_s; 127 struct TALER_Amount price; 128 const json_t *prices = NULL; 129 struct GNUNET_JSON_Specification spec[] = { 130 GNUNET_JSON_spec_mark_optional ( 131 GNUNET_JSON_spec_string_copy ("product_id", 132 &r->product_id), 133 NULL), 134 GNUNET_JSON_spec_mark_optional ( 135 GNUNET_JSON_spec_string_copy ("product_name", 136 &r->product_name), 137 NULL), 138 GNUNET_JSON_spec_mark_optional ( 139 GNUNET_JSON_spec_string_copy ("description", 140 &r->description), 141 NULL), 142 GNUNET_JSON_spec_mark_optional ( 143 GNUNET_JSON_spec_object_copy ("description_i18n", 144 &r->description_i18n), 145 NULL), 146 GNUNET_JSON_spec_mark_optional ( 147 GNUNET_JSON_spec_uint64 ("quantity", 148 &legacy_quantity), 149 &no_quantity), 150 GNUNET_JSON_spec_mark_optional ( 151 GNUNET_JSON_spec_bool ("prices_are_net", 152 &r->prices_are_net), 153 NULL), 154 GNUNET_JSON_spec_mark_optional ( 155 GNUNET_JSON_spec_string ("unit_quantity", 156 &unit_quantity_s), 157 &no_unit_quantity), 158 GNUNET_JSON_spec_mark_optional ( 159 GNUNET_JSON_spec_string_copy ("unit", 160 &r->unit), 161 NULL), 162 GNUNET_JSON_spec_mark_optional ( 163 TALER_JSON_spec_amount_any ("price", 164 &price), 165 &no_price), 166 GNUNET_JSON_spec_mark_optional ( 167 GNUNET_JSON_spec_array_const ("prices", 168 &prices), 169 NULL), 170 GNUNET_JSON_spec_mark_optional ( 171 GNUNET_JSON_spec_string_copy ("image", 172 &r->image), 173 NULL), 174 GNUNET_JSON_spec_mark_optional ( 175 GNUNET_JSON_spec_array_copy ("taxes", 176 &r->taxes), 177 NULL), 178 GNUNET_JSON_spec_mark_optional ( 179 GNUNET_JSON_spec_timestamp ("delivery_date", 180 &r->delivery_date), 181 NULL), 182 GNUNET_JSON_spec_mark_optional ( 183 GNUNET_JSON_spec_uint64 ("product_money_pot", 184 &r->product_money_pot), 185 NULL), 186 GNUNET_JSON_spec_end () 187 }; 188 enum GNUNET_GenericReturnValue res; 189 const char *ename; 190 unsigned int eline; 191 192 r->delivery_date = GNUNET_TIME_UNIT_FOREVER_TS; 193 res = GNUNET_JSON_parse (pj, 194 spec, 195 &ename, 196 &eline); 197 if (GNUNET_OK != res) 198 { 199 GNUNET_break (0); 200 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 201 "Failed to parse product at field %s\n", 202 ename); 203 return GNUNET_SYSERR; 204 } 205 if (! no_quantity) 206 { 207 r->unit_quantity.integer = legacy_quantity; 208 r->unit_quantity.fractional = 0; 209 } 210 if (! no_unit_quantity) 211 { 212 if (GNUNET_OK != 213 parse_unit_quantity (unit_quantity_s, 214 &r->unit_quantity)) 215 { 216 GNUNET_break (0); 217 return GNUNET_SYSERR; 218 } 219 } 220 if ( (! no_quantity) && (! no_unit_quantity) ) 221 { 222 GNUNET_break ( (0 == r->unit_quantity.fractional) && 223 (legacy_quantity == r->unit_quantity.integer) ); 224 } 225 if ( (NULL != r->image) && 226 (! TALER_MERCHANT_image_data_url_valid (r->image)) ) 227 { 228 GNUNET_break_op (0); 229 return GNUNET_SYSERR; 230 } 231 if ( (NULL != r->taxes) && 232 (! TALER_MERCHANT_taxes_array_valid (r->taxes)) ) 233 { 234 GNUNET_break_op (0); 235 return GNUNET_SYSERR; 236 } 237 if (NULL != prices) 238 { 239 size_t len = json_array_size (prices); 240 size_t i; 241 json_t *price_i; 242 243 GNUNET_assert (len < UINT_MAX); 244 r->prices_length = (unsigned int) len; 245 r->prices = GNUNET_new_array (r->prices_length, 246 struct TALER_Amount); 247 json_array_foreach (prices, i, price_i) 248 { 249 struct GNUNET_JSON_Specification pspec[] = { 250 TALER_JSON_spec_amount_any (NULL, 251 &r->prices[i]), 252 GNUNET_JSON_spec_end () 253 }; 254 255 res = GNUNET_JSON_parse (price_i, 256 pspec, 257 &ename, 258 &eline); 259 if (GNUNET_OK != res) 260 { 261 GNUNET_break (0); 262 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 263 "Failed to parse price at index %u\n", 264 (unsigned int) i); 265 return GNUNET_SYSERR; 266 } 267 } 268 } 269 else if (! no_price) 270 { 271 r->prices_length = 1; 272 r->prices = GNUNET_new_array (1, 273 struct TALER_Amount); 274 r->prices[0] = price; 275 } 276 return GNUNET_OK; 277 } 278 279 280 void 281 TALER_MERCHANT_product_sold_free (struct TALER_MERCHANT_ProductSold *product) 282 { 283 GNUNET_free (product->product_id); 284 GNUNET_free (product->product_name); 285 GNUNET_free (product->description); 286 json_decref (product->description_i18n); 287 GNUNET_free (product->prices); 288 GNUNET_free (product->unit); 289 GNUNET_free (product->image); 290 json_decref (product->taxes); 291 }