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 }