template_parse.c (11528B)
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 enum GNUNET_GenericReturnValue 251 TALER_MERCHANT_template_contract_parse ( 252 const json_t *template_contract, 253 struct TALER_MERCHANT_TemplateContract *out, 254 const char **error_name) 255 { 256 const char *template_type_str = NULL; 257 struct GNUNET_JSON_Specification spec[] = { 258 GNUNET_JSON_spec_mark_optional ( 259 GNUNET_JSON_spec_string ("template_type", 260 &template_type_str), 261 NULL), 262 GNUNET_JSON_spec_mark_optional ( 263 GNUNET_JSON_spec_string ("summary", 264 &out->summary), 265 NULL), 266 GNUNET_JSON_spec_mark_optional ( 267 GNUNET_JSON_spec_string ("currency", 268 &out->currency), 269 NULL), 270 GNUNET_JSON_spec_mark_optional ( 271 TALER_JSON_spec_amount_any ("amount", 272 &out->amount), 273 &out->no_amount), 274 GNUNET_JSON_spec_mark_optional ( 275 GNUNET_JSON_spec_uint32 ("minimum_age", 276 &out->minimum_age), 277 NULL), 278 GNUNET_JSON_spec_mark_optional ( 279 GNUNET_JSON_spec_relative_time ("pay_duration", 280 &out->pay_duration), 281 NULL), 282 GNUNET_JSON_spec_mark_optional ( 283 GNUNET_JSON_spec_relative_time ("max_pickup_duration", 284 &out->max_pickup_duration), 285 NULL), 286 GNUNET_JSON_spec_mark_optional ( 287 GNUNET_JSON_spec_bool ("request_tip", 288 &out->request_tip), 289 NULL), 290 GNUNET_JSON_spec_end () 291 }; 292 const char *en; 293 294 if (NULL == template_contract) 295 { 296 if (NULL != error_name) 297 *error_name = "template_contract is NULL"; 298 return GNUNET_SYSERR; 299 } 300 out->max_pickup_duration = GNUNET_TIME_UNIT_FOREVER_REL; 301 if (GNUNET_OK != 302 GNUNET_JSON_parse ((json_t *) template_contract, 303 spec, 304 &en, 305 NULL)) 306 { 307 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 308 "Invalid input for field %s\n", 309 en); 310 if (NULL != error_name) 311 *error_name = en; 312 return GNUNET_SYSERR; 313 } 314 315 out->type = TALER_MERCHANT_template_type_from_string (template_type_str); 316 if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type) 317 { 318 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 319 "Invalid template_type used '%s'\n", 320 template_type_str); 321 if (NULL != error_name) 322 *error_name = "Invalid template_type used"; 323 return GNUNET_SYSERR; 324 } 325 326 /* Parse additional fields for each specific type */ 327 switch (out->type) 328 { 329 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 330 return GNUNET_OK; 331 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 332 return parse_template_inventory (template_contract, 333 out, 334 error_name); 335 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 336 return parse_template_paivana (template_contract, 337 out, 338 error_name); 339 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 340 break; 341 } 342 343 /* I think we are never supposed to reach it */ 344 GNUNET_break_op (0); 345 if (NULL != error_name) 346 *error_name = "template_type"; 347 return GNUNET_SYSERR; 348 } 349 350 351 void 352 TALER_MERCHANT_template_contract_free ( 353 struct TALER_MERCHANT_TemplateContract *tc) 354 { 355 switch (tc->type) 356 { 357 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 358 return; 359 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 360 return; 361 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 362 for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++) 363 TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]); 364 GNUNET_array_grow (tc->details.paivana.choices, 365 tc->details.paivana.choices_len, 366 0); 367 return; 368 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 369 return; 370 } 371 } 372 373 374 bool 375 TALER_MERCHANT_template_contract_valid (const json_t *template_contract) 376 { 377 struct TALER_MERCHANT_TemplateContract tmp; 378 bool ret; 379 380 memset (&tmp, 381 0, 382 sizeof (tmp)); 383 ret = (GNUNET_OK == 384 TALER_MERCHANT_template_contract_parse (template_contract, 385 &tmp, 386 NULL)); 387 TALER_MERCHANT_template_contract_free (&tmp); 388 return ret; 389 }