template_parse.c (14871B)
1 /* 2 This file is part of TALER 3 (C) 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/template_parse.c 18 * @brief shared logic for template contract parsing 19 * @author Bohdan Potuzhnyi 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_common.h> 23 #include <gnunet/gnunet_json_lib.h> 24 #include <jansson.h> 25 #include <string.h> 26 #include <taler/taler_json_lib.h> 27 #include <taler/taler_util.h> 28 #include "taler/taler_merchant_util.h" 29 #include <regex.h> 30 31 32 enum TALER_MERCHANT_TemplateType 33 TALER_MERCHANT_template_type_from_contract (const json_t *template_contract) 34 { 35 const json_t *type_val; 36 37 if (NULL == template_contract) 38 return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 39 40 type_val = json_object_get (template_contract, 41 "template_type"); 42 43 if (NULL == type_val) 44 { 45 /* missing => fixed order */ 46 return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; 47 } 48 if (! json_is_string (type_val)) 49 { 50 GNUNET_break_op (0); 51 return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 52 } 53 54 return TALER_MERCHANT_template_type_from_string ( 55 json_string_value (type_val)); 56 } 57 58 59 enum TALER_MERCHANT_TemplateType 60 TALER_MERCHANT_template_type_from_string (const char *template_type) 61 { 62 if (NULL == template_type) 63 return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; 64 if (0 == strcmp (template_type, 65 "fixed-order")) 66 return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; 67 if (0 == strcmp (template_type, 68 "inventory-cart")) 69 return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART; 70 if (0 == strcmp (template_type, 71 "paivana")) 72 return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA; 73 return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 74 } 75 76 77 const char * 78 TALER_MERCHANT_template_type_to_string ( 79 enum TALER_MERCHANT_TemplateType template_type) 80 { 81 switch (template_type) 82 { 83 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 84 return "fixed-order"; 85 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 86 return "inventory-cart"; 87 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 88 return "paivana"; 89 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 90 break; 91 } 92 return NULL; 93 } 94 95 96 /** 97 * Parse inventory-specific fields from a template contract. 98 * 99 * @param template_contract json 100 * @param[out] out where to write parsed fields 101 * @param[out] error_name error description 102 * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure 103 */ 104 static enum GNUNET_GenericReturnValue 105 parse_template_inventory (const json_t *template_contract, 106 struct TALER_MERCHANT_TemplateContract *out, 107 const char **error_name) 108 { 109 struct GNUNET_JSON_Specification spec[] = { 110 GNUNET_JSON_spec_mark_optional ( 111 GNUNET_JSON_spec_bool ( 112 "selected_all", 113 &out->details.inventory.selected_all), 114 NULL), 115 GNUNET_JSON_spec_mark_optional ( 116 GNUNET_JSON_spec_array_const ( 117 "selected_categories", 118 &out->details.inventory.selected_categories) 119 , 120 NULL), 121 GNUNET_JSON_spec_mark_optional ( 122 GNUNET_JSON_spec_array_const ( 123 "selected_products", 124 &out->details.inventory.selected_products), 125 NULL), 126 GNUNET_JSON_spec_mark_optional ( 127 GNUNET_JSON_spec_bool ( 128 "choose_one", 129 &out->details.inventory.choose_one), 130 NULL), 131 GNUNET_JSON_spec_end () 132 }; 133 const char *en; 134 135 if (GNUNET_OK != 136 GNUNET_JSON_parse ((json_t *) template_contract, 137 spec, 138 &en, 139 NULL)) 140 { 141 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 142 "Invalid inventory template_contract for field %s\n", 143 en); 144 if (NULL != error_name) 145 *error_name = en; 146 return GNUNET_SYSERR; 147 } 148 149 if (NULL != out->details.inventory.selected_categories) 150 { 151 const json_t *entry; 152 size_t idx; 153 154 json_array_foreach ((json_t *) out->details.inventory.selected_categories, 155 idx, 156 entry) 157 { 158 if ( (! json_is_integer (entry)) || 159 (0 > json_integer_value (entry)) ) 160 { 161 GNUNET_break_op (0); 162 if (NULL != error_name) 163 *error_name = "selected_categories"; 164 return GNUNET_SYSERR; 165 } 166 } 167 } 168 169 if (NULL != out->details.inventory.selected_products) 170 { 171 const json_t *entry; 172 size_t idx; 173 174 json_array_foreach ((json_t *) out->details.inventory.selected_products, 175 idx, 176 entry) 177 { 178 if (! json_is_string (entry)) 179 { 180 GNUNET_break_op (0); 181 if (NULL != error_name) 182 *error_name = "selected_products"; 183 return GNUNET_SYSERR; 184 } 185 } 186 } 187 return GNUNET_OK; 188 } 189 190 191 /** 192 * Parse paivana-specific fields from a template contract. 193 * 194 * @param template_contract json 195 * @param[out] out where to write parsed fields 196 * @param[out] error_name error description 197 * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure 198 */ 199 static enum GNUNET_GenericReturnValue 200 parse_template_paivana (const json_t *template_contract, 201 struct TALER_MERCHANT_TemplateContract *out, 202 const char **error_name) 203 { 204 struct GNUNET_JSON_Specification spec[] = { 205 GNUNET_JSON_spec_mark_optional ( 206 GNUNET_JSON_spec_string ("website_regex", 207 &out->details.paivana.website_regex), 208 NULL), 209 TALER_MERCHANT_spec_order_choices ("choices", 210 &out->details.paivana.choices, 211 &out->details.paivana.choices_len), 212 GNUNET_JSON_spec_end () 213 }; 214 const char *en; 215 216 if (GNUNET_OK != 217 GNUNET_JSON_parse ((json_t *) template_contract, 218 spec, 219 &en, 220 NULL)) 221 { 222 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 223 "Invalid paivana template_contract for field %s\n", 224 en); 225 if (NULL != error_name) 226 *error_name = en; 227 return GNUNET_SYSERR; 228 } 229 if (NULL != out->details.paivana.website_regex) 230 { 231 regex_t ex; 232 233 if (0 != regcomp (&ex, 234 out->details.paivana.website_regex, 235 REG_NOSUB | REG_EXTENDED)) 236 { 237 GNUNET_break_op (0); 238 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 239 "Invalid paivana website_regex given\n"); 240 if (NULL != error_name) 241 *error_name = "Invalid website_regex given"; 242 return GNUNET_SYSERR; 243 } 244 regfree (&ex); 245 } 246 return GNUNET_OK; 247 } 248 249 250 /** 251 * Check that the (optional) @e min_amount and @e max_amount of @a tc are 252 * usable: they must be denominated in the same currency, which must be 253 * the currency of the template (which then must be given, as otherwise we 254 * could not tell in which currency to enforce the limits), and they must 255 * not exclude each other. 256 * 257 * @param tc template contract to check 258 * @param[out] error_name error description 259 * @return #GNUNET_OK on success, #GNUNET_SYSERR on validation failure 260 */ 261 static enum GNUNET_GenericReturnValue 262 check_amount_limits (const struct TALER_MERCHANT_TemplateContract *tc, 263 const char **error_name) 264 { 265 if (tc->no_min_amount && 266 tc->no_max_amount) 267 return GNUNET_OK; 268 if ( (! tc->no_min_amount) && 269 (! tc->no_max_amount) ) 270 { 271 /* Must be checked before the two are compared, as comparing 272 amounts of different currencies fails an assertion. Note that 273 we deliberately do not rely on both having been checked against 274 the currency of the template here. */ 275 if (GNUNET_YES != 276 TALER_amount_cmp_currency (&tc->min_amount, 277 &tc->max_amount)) 278 { 279 GNUNET_break_op (0); 280 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 281 "min_amount and max_amount must use the same currency\n"); 282 if (NULL != error_name) 283 *error_name = "max_amount"; 284 return GNUNET_SYSERR; 285 } 286 if (0 < TALER_amount_cmp (&tc->min_amount, 287 &tc->max_amount)) 288 { 289 GNUNET_break_op (0); 290 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 291 "min_amount exceeds max_amount\n"); 292 if (NULL != error_name) 293 *error_name = "min_amount"; 294 return GNUNET_SYSERR; 295 } 296 } 297 if (NULL == tc->currency) 298 { 299 GNUNET_break_op (0); 300 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 301 "Template with min_amount or max_amount must specify a currency\n"); 302 if (NULL != error_name) 303 *error_name = "currency"; 304 return GNUNET_SYSERR; 305 } 306 if ( (! tc->no_min_amount) && 307 (0 != strcasecmp (tc->currency, 308 tc->min_amount.currency)) ) 309 { 310 GNUNET_break_op (0); 311 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 312 "min_amount must use the currency of the template (%s)\n", 313 tc->currency); 314 if (NULL != error_name) 315 *error_name = "min_amount"; 316 return GNUNET_SYSERR; 317 } 318 if ( (! tc->no_max_amount) && 319 (0 != strcasecmp (tc->currency, 320 tc->max_amount.currency)) ) 321 { 322 GNUNET_break_op (0); 323 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 324 "max_amount must use the currency of the template (%s)\n", 325 tc->currency); 326 if (NULL != error_name) 327 *error_name = "max_amount"; 328 return GNUNET_SYSERR; 329 } 330 return GNUNET_OK; 331 } 332 333 334 enum GNUNET_GenericReturnValue 335 TALER_MERCHANT_template_contract_parse ( 336 const json_t *template_contract, 337 struct TALER_MERCHANT_TemplateContract *out, 338 const char **error_name) 339 { 340 const char *template_type_str = NULL; 341 struct GNUNET_JSON_Specification spec[] = { 342 GNUNET_JSON_spec_mark_optional ( 343 GNUNET_JSON_spec_string ("template_type", 344 &template_type_str), 345 NULL), 346 GNUNET_JSON_spec_mark_optional ( 347 GNUNET_JSON_spec_string ("summary", 348 &out->summary), 349 NULL), 350 GNUNET_JSON_spec_mark_optional ( 351 GNUNET_JSON_spec_string ("currency", 352 &out->currency), 353 NULL), 354 GNUNET_JSON_spec_mark_optional ( 355 TALER_JSON_spec_amount_any ("amount", 356 &out->amount), 357 &out->no_amount), 358 GNUNET_JSON_spec_mark_optional ( 359 TALER_JSON_spec_amount_any ("min_amount", 360 &out->min_amount), 361 &out->no_min_amount), 362 GNUNET_JSON_spec_mark_optional ( 363 TALER_JSON_spec_amount_any ("max_amount", 364 &out->max_amount), 365 &out->no_max_amount), 366 GNUNET_JSON_spec_mark_optional ( 367 GNUNET_JSON_spec_uint32 ("minimum_age", 368 &out->minimum_age), 369 NULL), 370 GNUNET_JSON_spec_mark_optional ( 371 GNUNET_JSON_spec_relative_time ("pay_duration", 372 &out->pay_duration), 373 NULL), 374 GNUNET_JSON_spec_mark_optional ( 375 GNUNET_JSON_spec_relative_time ("max_pickup_duration", 376 &out->max_pickup_duration), 377 NULL), 378 GNUNET_JSON_spec_mark_optional ( 379 GNUNET_JSON_spec_bool ("request_tip", 380 &out->request_tip), 381 NULL), 382 GNUNET_JSON_spec_end () 383 }; 384 const char *en; 385 386 if (NULL == template_contract) 387 { 388 if (NULL != error_name) 389 *error_name = "template_contract is NULL"; 390 return GNUNET_SYSERR; 391 } 392 out->max_pickup_duration = GNUNET_TIME_UNIT_FOREVER_REL; 393 out->no_min_amount = true; 394 out->no_max_amount = true; 395 if (GNUNET_OK != 396 GNUNET_JSON_parse ((json_t *) template_contract, 397 spec, 398 &en, 399 NULL)) 400 { 401 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 402 "Invalid input for field %s\n", 403 en); 404 if (NULL != error_name) 405 *error_name = en; 406 return GNUNET_SYSERR; 407 } 408 if (GNUNET_OK != 409 check_amount_limits (out, 410 error_name)) 411 return GNUNET_SYSERR; 412 413 out->type = TALER_MERCHANT_template_type_from_string (template_type_str); 414 if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type) 415 { 416 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 417 "Invalid template_type used '%s'\n", 418 template_type_str); 419 if (NULL != error_name) 420 *error_name = "Invalid template_type used"; 421 return GNUNET_SYSERR; 422 } 423 424 /* Parse additional fields for each specific type */ 425 switch (out->type) 426 { 427 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 428 return GNUNET_OK; 429 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 430 return parse_template_inventory (template_contract, 431 out, 432 error_name); 433 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 434 return parse_template_paivana (template_contract, 435 out, 436 error_name); 437 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 438 break; 439 } 440 441 /* I think we are never supposed to reach it */ 442 GNUNET_break_op (0); 443 if (NULL != error_name) 444 *error_name = "template_type"; 445 return GNUNET_SYSERR; 446 } 447 448 449 void 450 TALER_MERCHANT_template_contract_free ( 451 struct TALER_MERCHANT_TemplateContract *tc) 452 { 453 switch (tc->type) 454 { 455 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 456 return; 457 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 458 return; 459 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 460 for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++) 461 TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]); 462 GNUNET_array_grow (tc->details.paivana.choices, 463 tc->details.paivana.choices_len, 464 0); 465 return; 466 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 467 return; 468 } 469 } 470 471 472 bool 473 TALER_MERCHANT_template_contract_valid (const json_t *template_contract) 474 { 475 struct TALER_MERCHANT_TemplateContract tmp; 476 bool ret; 477 478 memset (&tmp, 479 0, 480 sizeof (tmp)); 481 ret = (GNUNET_OK == 482 TALER_MERCHANT_template_contract_parse (template_contract, 483 &tmp, 484 NULL)); 485 TALER_MERCHANT_template_contract_free (&tmp); 486 return ret; 487 }