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