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