contract_parse.c (12602B)
1 /* 2 This file is part of TALER 3 (C) 2024, 2025, 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/contract_parse.c 18 * @brief shared logic for contract terms 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 * Parse v0-specific fields of @a input JSON into @a contract. 35 * 36 * @param[in] input the JSON contract terms 37 * @param[out] contract where to write the data 38 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error 39 */ 40 static enum GNUNET_GenericReturnValue 41 parse_contract_v0 ( 42 json_t *input, 43 struct TALER_MERCHANT_ProtoContract *contract) 44 { 45 struct GNUNET_JSON_Specification espec[] = { 46 TALER_JSON_spec_amount_any ("amount", 47 &contract->details.v0.brutto), 48 GNUNET_JSON_spec_mark_optional ( 49 TALER_JSON_spec_amount_any ("tip", 50 &contract->details.v0.tip), 51 &contract->details.v0.no_tip), 52 TALER_JSON_spec_amount_any ("max_fee", 53 &contract->details.v0.max_fee), 54 GNUNET_JSON_spec_end () 55 }; 56 enum GNUNET_GenericReturnValue res; 57 const char *ename; 58 unsigned int eline; 59 60 res = GNUNET_JSON_parse (input, 61 espec, 62 &ename, 63 &eline); 64 if (GNUNET_OK != res) 65 { 66 GNUNET_break (0); 67 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 68 "Failed to parse contract v0 at field %s\n", 69 ename); 70 return GNUNET_SYSERR; 71 } 72 73 if (GNUNET_OK != 74 TALER_amount_cmp_currency (&contract->details.v0.max_fee, 75 &contract->details.v0.brutto)) 76 { 77 GNUNET_break (0); 78 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 79 "'max_fee' does not match currency of contract price"); 80 return GNUNET_SYSERR; 81 } 82 if ( (! contract->details.v0.no_tip) && 83 (GNUNET_OK != 84 TALER_amount_cmp_currency (&contract->details.v0.tip, 85 &contract->details.v0.brutto)) ) 86 { 87 GNUNET_break (0); 88 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 89 "'tip' does not match currency of contract price"); 90 return GNUNET_SYSERR; 91 } 92 if (NULL != contract->base->amount_external) 93 { 94 if (! TALER_MERCHANT_amount_external_valid ( 95 contract->base->amount_external)) 96 { 97 GNUNET_break_op (0); 98 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 99 "'amount_external' is not a valid set of external payments"); 100 return GNUNET_SYSERR; 101 } 102 if (! TALER_MERCHANT_amount_external_currency_valid ( 103 contract->base->amount_external, 104 &contract->details.v0.brutto)) 105 { 106 GNUNET_break_op (0); 107 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 108 "'amount_external' does not match currency of contract price"); 109 return GNUNET_SYSERR; 110 } 111 if (! TALER_MERCHANT_amount_external_total_valid ( 112 contract->base->amount_external, 113 &contract->details.v0.brutto)) 114 { 115 GNUNET_break_op (0); 116 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 117 "total of 'amount_external' and contract price is too large"); 118 return GNUNET_SYSERR; 119 } 120 } 121 122 return res; 123 } 124 125 126 /** 127 * Parse v1-specific fields of @a input JSON into @a pc. 128 * 129 * @param[in] input the JSON contract terms 130 * @param[out] pc where to write the data 131 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error 132 */ 133 static enum GNUNET_GenericReturnValue 134 parse_contract_v1 ( 135 json_t *input, 136 struct TALER_MERCHANT_ProtoContract *pc) 137 { 138 struct GNUNET_JSON_Specification espec[] = { 139 TALER_MERCHANT_spec_contract_choices ( 140 "choices", 141 &pc->details.v1.choices, 142 &pc->details.v1.choices_len), 143 TALER_MERCHANT_spec_token_families ( 144 "token_families", 145 &pc->details.v1.token_authorities, 146 &pc->details.v1.token_authorities_len), 147 GNUNET_JSON_spec_end () 148 }; 149 150 enum GNUNET_GenericReturnValue res; 151 const char *ename; 152 unsigned int eline; 153 154 res = GNUNET_JSON_parse (input, 155 espec, 156 &ename, 157 &eline); 158 if (GNUNET_OK != res) 159 { 160 GNUNET_break (0); 161 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 162 "Failed to parse contract v1 at field %s\n", 163 ename); 164 return GNUNET_SYSERR; 165 } 166 if (NULL != pc->base->amount_external) 167 { 168 if (! TALER_MERCHANT_amount_external_valid (pc->base->amount_external)) 169 { 170 GNUNET_break_op (0); 171 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 172 "'amount_external' is not a valid set of external payments\n"); 173 return GNUNET_SYSERR; 174 } 175 for (unsigned int i = 0; i<pc->details.v1.choices_len; i++) 176 { 177 if (! TALER_MERCHANT_amount_external_currency_valid ( 178 pc->base->amount_external, 179 &pc->details.v1.choices[i].amount)) 180 { 181 GNUNET_break_op (0); 182 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 183 "'amount_external' does not match currency of choice #%u\n", 184 i); 185 return GNUNET_SYSERR; 186 } 187 if (! TALER_MERCHANT_amount_external_total_valid ( 188 pc->base->amount_external, 189 &pc->details.v1.choices[i].amount)) 190 { 191 GNUNET_break_op (0); 192 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 193 "total of 'amount_external' and choice #%u is too large\n", 194 i); 195 return GNUNET_SYSERR; 196 } 197 } 198 } 199 200 return res; 201 } 202 203 204 struct TALER_MERCHANT_ProtoContract * 205 TALER_MERCHANT_proto_contract_parse ( 206 json_t *input) 207 { 208 struct TALER_MERCHANT_ContractBaseTerms *base; 209 struct TALER_MERCHANT_ProtoContract *pc; 210 enum GNUNET_GenericReturnValue res; 211 212 base = TALER_MERCHANT_base_terms_parse (input); 213 if (NULL == base) 214 { 215 GNUNET_break_op (0); 216 return NULL; 217 } 218 pc = GNUNET_new (struct TALER_MERCHANT_ProtoContract); 219 pc->base = base; 220 { 221 const json_t *products = NULL; 222 struct GNUNET_JSON_Specification espec[] = { 223 TALER_JSON_spec_slug_copy ("order_id", 224 &pc->order_id), 225 GNUNET_JSON_spec_mark_optional ( 226 GNUNET_JSON_spec_array_const ("products", 227 &products), 228 NULL), 229 GNUNET_JSON_spec_timestamp ("timestamp", 230 &pc->timestamp), 231 GNUNET_JSON_spec_timestamp ("refund_deadline", 232 &pc->refund_deadline), 233 GNUNET_JSON_spec_timestamp ("pay_deadline", 234 &pc->pay_deadline), 235 GNUNET_JSON_spec_timestamp ("wire_transfer_deadline", 236 &pc->wire_deadline), 237 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 238 &pc->merchant_pub), 239 GNUNET_JSON_spec_fixed_auto ("h_wire", 240 &pc->h_wire), 241 TALER_JSON_spec_web_url_copy ("merchant_base_url", 242 &pc->merchant_base_url), 243 TALER_MERCHANT_spec_merchant_details ("merchant", 244 &pc->merchant), 245 GNUNET_JSON_spec_string_copy ("wire_method", 246 &pc->wire_method), 247 GNUNET_JSON_spec_array_copy ("exchanges", 248 &pc->exchanges), 249 250 GNUNET_JSON_spec_end () 251 }; 252 const char *ename; 253 unsigned int eline; 254 255 res = GNUNET_JSON_parse (input, 256 espec, 257 &ename, 258 &eline); 259 if (GNUNET_OK != res) 260 { 261 GNUNET_break_op (0); 262 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 263 "Failed to parse proto contract at field %s\n", 264 ename); 265 goto cleanup; 266 } 267 if (NULL != products) 268 { 269 pc->products_len = json_array_size (products); 270 if (0 != pc->products_len) 271 { 272 size_t i; 273 json_t *p; 274 275 pc->products = GNUNET_new_array ( 276 pc->products_len, 277 struct TALER_MERCHANT_ProductSold); 278 json_array_foreach (products, i, p) 279 { 280 if (GNUNET_OK != 281 TALER_MERCHANT_parse_product_sold (p, 282 &pc->products[i], 283 false)) 284 { 285 GNUNET_break (0); 286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 287 "Failed to parse product at offset %u\n", 288 (unsigned int) i); 289 goto cleanup; 290 } 291 } 292 } 293 } 294 } 295 switch (base->version) 296 { 297 case TALER_MERCHANT_CONTRACT_VERSION_0: 298 if (GNUNET_OK == 299 parse_contract_v0 (input, 300 pc)) 301 return pc; 302 GNUNET_break_op (0); 303 break; 304 case TALER_MERCHANT_CONTRACT_VERSION_1: 305 if (GNUNET_OK == 306 parse_contract_v1 (input, 307 pc)) 308 return pc; 309 GNUNET_break_op (0); 310 break; 311 } 312 cleanup: 313 TALER_MERCHANT_proto_contract_free (pc); 314 return NULL; 315 } 316 317 318 /** 319 * Free the proto-contract at @a pc 320 * 321 * @param[in] pc proto-contract to free 322 */ 323 void 324 TALER_MERCHANT_proto_contract_free ( 325 struct TALER_MERCHANT_ProtoContract *pc) 326 { 327 if (NULL != pc->products) 328 { 329 for (size_t i = 0; i<pc->products_len; i++) 330 TALER_MERCHANT_product_sold_free (&pc->products[i]); 331 GNUNET_free (pc->products); 332 } 333 if (NULL != pc->base) 334 { 335 switch (pc->base->version) 336 { 337 case TALER_MERCHANT_CONTRACT_VERSION_0: 338 break; 339 case TALER_MERCHANT_CONTRACT_VERSION_1: 340 for (unsigned int i = 0; 341 i < pc->details.v1.choices_len; 342 i++) 343 TALER_MERCHANT_contract_choice_free ( 344 &pc->details.v1.choices[i]); 345 GNUNET_free (pc->details.v1.choices); 346 for (unsigned int i = 0; 347 i < pc->details.v1.token_authorities_len; 348 i++) 349 TALER_MERCHANT_contract_token_family_free ( 350 &pc->details.v1.token_authorities[i]); 351 GNUNET_free (pc->details.v1.token_authorities); 352 break; 353 } 354 TALER_MERCHANT_base_terms_free (pc->base); 355 pc->base = NULL; 356 } 357 GNUNET_free (pc->wire_method); 358 GNUNET_free (pc->merchant_base_url); 359 TALER_MERCHANT_metadata_free (&pc->merchant); 360 if (NULL != pc->exchanges) 361 { 362 json_decref (pc->exchanges); 363 pc->exchanges = NULL; 364 } 365 GNUNET_free (pc->order_id); 366 GNUNET_free (pc); 367 } 368 369 370 struct TALER_MERCHANT_Contract * 371 TALER_MERCHANT_contract_parse (json_t *input) 372 { 373 struct TALER_MERCHANT_Contract *contract 374 = GNUNET_new (struct TALER_MERCHANT_Contract); 375 struct GNUNET_JSON_Specification espec[] = { 376 GNUNET_JSON_spec_string_copy ("nonce", 377 &contract->nonce), 378 GNUNET_JSON_spec_end () 379 }; 380 enum GNUNET_GenericReturnValue res; 381 const char *ename; 382 unsigned int eline; 383 384 GNUNET_assert (NULL != input); 385 contract->pc = TALER_MERCHANT_proto_contract_parse (input); 386 if (NULL == contract->pc) 387 { 388 GNUNET_break_op (0); 389 GNUNET_free (contract); 390 return NULL; 391 } 392 res = GNUNET_JSON_parse (input, 393 espec, 394 &ename, 395 &eline); 396 if (GNUNET_OK != res) 397 { 398 GNUNET_break_op (0); 399 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 400 "Failed to parse contract at field %s\n", 401 ename); 402 goto cleanup; 403 } 404 return contract; 405 406 cleanup: 407 TALER_MERCHANT_contract_free (contract); 408 return NULL; 409 } 410 411 412 void 413 TALER_MERCHANT_contract_free ( 414 struct TALER_MERCHANT_Contract *contract) 415 { 416 if (NULL == contract) 417 return; 418 if (NULL != contract->pc) 419 { 420 TALER_MERCHANT_proto_contract_free (contract->pc); 421 contract->pc = NULL; 422 } 423 GNUNET_free (contract->nonce); 424 GNUNET_free (contract); 425 }