Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2024 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 util/contract_parse.c
18 : * @brief shared logic for contract terms parsing
19 : * @author Iván Ávalos
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_common.h>
23 : #include <gnunet/gnunet_json_lib.h>
24 : #include <jansson.h>
25 : #include <stdbool.h>
26 : #include <stdint.h>
27 : #include <taler/taler_json_lib.h>
28 : #include <taler/taler_util.h>
29 : #include "taler_merchant_util.h"
30 :
31 :
32 : /**
33 : * Parse merchant details of given JSON contract terms.
34 : *
35 : * @param cls closure, unused parameter
36 : * @param root the JSON object representing data
37 : * @param[out] spec where to write the data
38 : * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
39 : */
40 : static enum GNUNET_GenericReturnValue
41 152 : parse_merchant_details (void *cls,
42 : json_t *root,
43 : struct GNUNET_JSON_Specification *ospec)
44 : {
45 152 : struct TALER_MERCHANT_Contract *contract = ospec->ptr;
46 : struct GNUNET_JSON_Specification spec[] = {
47 152 : GNUNET_JSON_spec_string_copy ("name",
48 : &contract->merchant.name),
49 152 : GNUNET_JSON_spec_mark_optional (
50 : GNUNET_JSON_spec_string_copy ("email",
51 : &contract->merchant.email),
52 : NULL),
53 152 : GNUNET_JSON_spec_mark_optional (
54 : GNUNET_JSON_spec_string_copy ("website",
55 : &contract->merchant.website),
56 : NULL),
57 152 : GNUNET_JSON_spec_mark_optional (
58 : GNUNET_JSON_spec_string_copy ("logo",
59 : &contract->merchant.logo),
60 : NULL),
61 152 : GNUNET_JSON_spec_mark_optional (
62 : GNUNET_JSON_spec_object_copy ("address",
63 : &contract->merchant.address),
64 : NULL),
65 152 : GNUNET_JSON_spec_mark_optional (
66 : GNUNET_JSON_spec_object_copy ("jurisdiction",
67 : &contract->merchant.jurisdiction),
68 : NULL),
69 152 : GNUNET_JSON_spec_end ()
70 : };
71 : const char *error_name;
72 : unsigned int error_line;
73 :
74 : (void) cls;
75 152 : if (GNUNET_OK !=
76 152 : GNUNET_JSON_parse (root,
77 : spec,
78 : &error_name,
79 : &error_line))
80 : {
81 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
82 : "Failed to parse %s at %u: %s\n",
83 : spec[error_line].field,
84 : error_line,
85 : error_name);
86 0 : GNUNET_break_op (0);
87 0 : return GNUNET_SYSERR;
88 : }
89 152 : return GNUNET_OK;
90 : }
91 :
92 :
93 : /**
94 : * Provide specification to parse given JSON object to merchant details in the
95 : * contract terms. All fields from @a contract are copied.
96 : *
97 : * @param name name of the merchant details field in the JSON
98 : * @param[out] contract where the merchant details have to be written
99 : */
100 : static struct GNUNET_JSON_Specification
101 152 : spec_merchant_details (const char *name,
102 : struct TALER_MERCHANT_Contract *contract)
103 : {
104 152 : struct GNUNET_JSON_Specification ret = {
105 : .parser = &parse_merchant_details,
106 : .field = name,
107 : .ptr = contract,
108 : };
109 :
110 152 : return ret;
111 : }
112 :
113 :
114 : /**
115 : * Get enum value from contract token type string.
116 : *
117 : * @param str contract token type string
118 : * @return enum value of token type
119 : */
120 : static enum TALER_MERCHANT_ContractTokenKind
121 9 : contract_token_kind_from_string (const char *str)
122 : {
123 9 : if (0 == strcmp ("subscription",
124 : str))
125 7 : return TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION;
126 2 : if (0 == strcmp ("discount",
127 : str))
128 2 : return TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT;
129 0 : return TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID;
130 : }
131 :
132 :
133 : enum GNUNET_GenericReturnValue
134 15 : TALER_MERCHANT_parse_choice_input (
135 : json_t *root,
136 : struct TALER_MERCHANT_ContractInput *input,
137 : size_t index,
138 : bool order)
139 : {
140 : const char *ename;
141 : unsigned int eline;
142 : struct GNUNET_JSON_Specification ispec[] = {
143 15 : TALER_MERCHANT_json_spec_cit ("type",
144 : &input->type),
145 15 : GNUNET_JSON_spec_end ()
146 : };
147 :
148 15 : if (GNUNET_OK !=
149 15 : GNUNET_JSON_parse (root,
150 : ispec,
151 : &ename,
152 : &eline))
153 : {
154 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
155 : "Failed to parse %s at %u: %s\n",
156 : ispec[eline].field,
157 : eline,
158 : ename);
159 0 : GNUNET_break_op (0);
160 0 : return GNUNET_SYSERR;
161 : }
162 :
163 15 : switch (input->type)
164 : {
165 0 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
166 0 : GNUNET_break (0);
167 0 : break;
168 15 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
169 : {
170 : struct GNUNET_JSON_Specification spec[] = {
171 15 : GNUNET_JSON_spec_string ("token_family_slug",
172 : &input->details.token.token_family_slug),
173 15 : GNUNET_JSON_spec_mark_optional (
174 : GNUNET_JSON_spec_uint32 ("count",
175 15 : &input->details.token.count),
176 : NULL),
177 15 : GNUNET_JSON_spec_end ()
178 : };
179 :
180 15 : if (GNUNET_OK !=
181 15 : GNUNET_JSON_parse (root,
182 : spec,
183 : &ename,
184 : &eline))
185 : {
186 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
187 : "Failed to parse %s at %u: %s\n",
188 : spec[eline].field,
189 : eline,
190 : ename);
191 0 : GNUNET_break_op (0);
192 0 : return GNUNET_SYSERR;
193 : }
194 :
195 15 : return GNUNET_OK;
196 : }
197 : }
198 :
199 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
200 : "Field 'type' invalid in input #%u\n",
201 : (unsigned int) index);
202 0 : GNUNET_break_op (0);
203 0 : return GNUNET_SYSERR;
204 : }
205 :
206 :
207 : enum GNUNET_GenericReturnValue
208 16 : TALER_MERCHANT_parse_choice_output (
209 : json_t *root,
210 : struct TALER_MERCHANT_ContractOutput *output,
211 : size_t index,
212 : bool order)
213 : {
214 : const char *ename;
215 : unsigned int eline;
216 : struct GNUNET_JSON_Specification ispec[] = {
217 16 : TALER_MERCHANT_json_spec_cot ("type",
218 : &output->type),
219 16 : GNUNET_JSON_spec_end ()
220 : };
221 :
222 16 : if (GNUNET_OK !=
223 16 : GNUNET_JSON_parse (root,
224 : ispec,
225 : &ename,
226 : &eline))
227 : {
228 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
229 : "Failed to parse %s at %u: %s\n",
230 : ispec[eline].field,
231 : eline,
232 : ename);
233 0 : GNUNET_break_op (0);
234 0 : return GNUNET_SYSERR;
235 : }
236 :
237 16 : switch (output->type)
238 : {
239 0 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
240 0 : GNUNET_break (0);
241 0 : break;
242 15 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
243 : {
244 : struct GNUNET_JSON_Specification spec[] = {
245 15 : GNUNET_JSON_spec_string ("token_family_slug",
246 : &output->details.token.token_family_slug),
247 15 : GNUNET_JSON_spec_mark_optional (
248 : GNUNET_JSON_spec_uint ("count",
249 : &output->details.token.count),
250 : NULL),
251 15 : GNUNET_JSON_spec_mark_optional (
252 : GNUNET_JSON_spec_timestamp ("valid_at",
253 : &output->details.token.valid_at),
254 : NULL),
255 15 : (! order)
256 7 : ? GNUNET_JSON_spec_uint ("key_index",
257 : &output->details.token.key_index)
258 15 : : GNUNET_JSON_spec_end (),
259 15 : GNUNET_JSON_spec_end ()
260 : };
261 :
262 15 : if (GNUNET_OK !=
263 15 : GNUNET_JSON_parse (root,
264 : spec,
265 : &ename,
266 : &eline))
267 : {
268 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
269 : "Failed to parse %s at %u: %s\n",
270 : spec[eline].field,
271 : eline,
272 : ename);
273 0 : GNUNET_break_op (0);
274 0 : return GNUNET_SYSERR;
275 : }
276 :
277 15 : return GNUNET_OK;
278 : }
279 1 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
280 : {
281 1 : const json_t *donau_urls = NULL;
282 : struct GNUNET_JSON_Specification spec[] = {
283 1 : GNUNET_JSON_spec_mark_optional (
284 : TALER_JSON_spec_amount_any ("amount",
285 : &output->details.donation_receipt.amount),
286 : NULL),
287 1 : (! order)
288 1 : ? GNUNET_JSON_spec_array_const ("donau_urls",
289 : &donau_urls)
290 1 : : GNUNET_JSON_spec_end (),
291 1 : GNUNET_JSON_spec_end ()
292 : };
293 :
294 1 : if (GNUNET_OK !=
295 1 : GNUNET_JSON_parse (root,
296 : spec,
297 : &ename,
298 : &eline))
299 : {
300 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
301 : "Failed to parse %s at %u: %s\n",
302 : spec[eline].field,
303 : eline,
304 : ename);
305 0 : GNUNET_break_op (0);
306 0 : return GNUNET_SYSERR;
307 : }
308 :
309 1 : GNUNET_array_grow (output->details.donation_receipt.donau_urls,
310 : output->details.donation_receipt.donau_urls_len,
311 : json_array_size (donau_urls));
312 :
313 1 : for (unsigned int i = 0;
314 4 : i < output->details.donation_receipt.donau_urls_len;
315 3 : i++)
316 : {
317 : const json_t *jurl;
318 :
319 3 : jurl = json_array_get (donau_urls,
320 : i);
321 3 : if (! json_is_string (jurl))
322 : {
323 0 : GNUNET_break_op (0);
324 0 : return GNUNET_SYSERR;
325 : }
326 :
327 3 : output->details.donation_receipt.donau_urls[i] =
328 3 : json_string_value (jurl);
329 : }
330 :
331 1 : return GNUNET_OK;
332 : }
333 : }
334 :
335 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
336 : "Field 'type' invalid in output #%u\n",
337 : (unsigned int) index);
338 0 : GNUNET_break_op (0);
339 0 : return GNUNET_SYSERR;
340 : }
341 :
342 :
343 : /**
344 : * Parse given JSON object to choices array.
345 : *
346 : * @param cls closure, pointer to array length
347 : * @param root the json array representing the choices
348 : * @param[out] ospec where to write the data
349 :
350 : * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
351 : */
352 : static enum GNUNET_GenericReturnValue
353 8 : parse_choices (
354 : void *cls,
355 : json_t *root,
356 : struct GNUNET_JSON_Specification *ospec)
357 : {
358 8 : struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr;
359 8 : unsigned int *choices_len = cls;
360 :
361 8 : if (! json_is_array (root))
362 : {
363 0 : GNUNET_break_op (0);
364 0 : return GNUNET_SYSERR;
365 : }
366 :
367 8 : GNUNET_array_grow (*choices,
368 : *choices_len,
369 : json_array_size (root));
370 :
371 17 : for (unsigned int i = 0; i < *choices_len; i++)
372 : {
373 9 : struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i];
374 : const json_t *jinputs;
375 : const json_t *joutputs;
376 :
377 : struct GNUNET_JSON_Specification spec[] = {
378 9 : TALER_JSON_spec_amount_any ("amount",
379 : &choice->amount),
380 9 : GNUNET_JSON_spec_mark_optional (
381 : GNUNET_JSON_spec_string_copy ("description",
382 : &choice->description),
383 : NULL),
384 9 : GNUNET_JSON_spec_mark_optional (
385 : GNUNET_JSON_spec_object_copy ("description_i18n",
386 : &choice->description_i18n),
387 : NULL),
388 9 : TALER_JSON_spec_amount_any ("max_fee",
389 : &choice->max_fee),
390 9 : GNUNET_JSON_spec_array_const ("inputs",
391 : &jinputs),
392 9 : GNUNET_JSON_spec_array_const ("outputs",
393 : &joutputs),
394 9 : GNUNET_JSON_spec_end ()
395 : };
396 :
397 : const char *ename;
398 : unsigned int eline;
399 :
400 9 : if (GNUNET_OK !=
401 9 : GNUNET_JSON_parse (json_array_get (root, i),
402 : spec,
403 : &ename,
404 : &eline))
405 : {
406 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
407 : "Failed to parse %s at %u: %s\n",
408 : spec[eline].field,
409 : eline,
410 : ename);
411 0 : GNUNET_break_op (0);
412 0 : return GNUNET_SYSERR;
413 : }
414 :
415 : {
416 : const json_t *jinput;
417 : size_t idx;
418 15 : json_array_foreach ((json_t *) jinputs, idx, jinput)
419 : {
420 6 : struct TALER_MERCHANT_ContractInput input = {
421 : .details.token.count = 1
422 : };
423 :
424 6 : if (GNUNET_OK !=
425 6 : TALER_MERCHANT_parse_choice_input ((json_t *) jinput,
426 : &input,
427 : idx,
428 : false))
429 0 : return GNUNET_SYSERR;
430 :
431 6 : switch (input.type)
432 : {
433 0 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
434 0 : GNUNET_break_op (0);
435 0 : return GNUNET_SYSERR;
436 6 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
437 : /* Ignore inputs tokens with 'count' field set to 0 */
438 6 : if (0 == input.details.token.count)
439 0 : continue;
440 6 : break;
441 : }
442 :
443 6 : GNUNET_array_append (choice->inputs,
444 : choice->inputs_len,
445 : input);
446 : }
447 : }
448 :
449 : {
450 : const json_t *joutput;
451 : size_t idx;
452 17 : json_array_foreach ((json_t *) joutputs, idx, joutput)
453 : {
454 8 : struct TALER_MERCHANT_ContractOutput output = {
455 : .details.token.count = 1
456 : };
457 :
458 8 : if (GNUNET_OK !=
459 8 : TALER_MERCHANT_parse_choice_output ((json_t *) joutput,
460 : &output,
461 : idx,
462 : false))
463 0 : return GNUNET_SYSERR;
464 :
465 8 : switch (output.type)
466 : {
467 0 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
468 0 : GNUNET_break_op (0);
469 0 : return GNUNET_SYSERR;
470 7 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
471 : /* Ignore output tokens with 'count' field set to 0 */
472 7 : if (0 == output.details.token.count)
473 0 : continue;
474 7 : break;
475 1 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
476 1 : break;
477 : }
478 :
479 8 : GNUNET_array_append (choice->outputs,
480 : choice->outputs_len,
481 : output);
482 : }
483 : }
484 : }
485 :
486 8 : return GNUNET_OK;
487 : }
488 :
489 :
490 : /**
491 : * Provide specification to parse given JSON array to contract terms
492 : * choices. All fields from @a choices elements are copied.
493 : *
494 : * @param name name of the choices field in the JSON
495 : * @param[out] choices where the contract choices array has to be written
496 : * @param[out] choices_len length of the @a choices array
497 : */
498 : static struct GNUNET_JSON_Specification
499 8 : spec_choices (
500 : const char *name,
501 : struct TALER_MERCHANT_ContractChoice **choices,
502 : unsigned int *choices_len)
503 : {
504 8 : struct GNUNET_JSON_Specification ret = {
505 : .cls = (void *) choices_len,
506 : .parser = &parse_choices,
507 : .field = name,
508 : .ptr = choices,
509 : };
510 :
511 8 : return ret;
512 : }
513 :
514 :
515 : /**
516 : * Free all the fields in the given @a choice, but not @a choice itself, since
517 : * it is normally part of an array.
518 : *
519 : * @param[in] choice contract terms choice to free
520 : */
521 : static void
522 9 : contract_choice_free (
523 : struct TALER_MERCHANT_ContractChoice *choice)
524 : {
525 17 : for (unsigned int i = 0; i < choice->outputs_len; i++)
526 : {
527 8 : struct TALER_MERCHANT_ContractOutput *output = &choice->outputs[i];
528 :
529 8 : GNUNET_free (output->details.coin.exchange_url);
530 : }
531 9 : GNUNET_free (choice->description);
532 9 : if (NULL != choice->description_i18n)
533 : {
534 1 : json_decref (choice->description_i18n);
535 1 : choice->description_i18n = NULL;
536 : }
537 9 : GNUNET_free (choice->inputs);
538 9 : GNUNET_free (choice->outputs);
539 9 : }
540 :
541 :
542 : /**
543 : * Parse token details of the given JSON contract token family.
544 : *
545 : * @param cls closure, unused parameter
546 : * @param root the JSON object representing data
547 : * @param[out] ospec where to write the data
548 : * @return #GNUNET_OK upon successful parsing; @GNUNET_SYSERR upon error
549 : */
550 : static enum GNUNET_GenericReturnValue
551 9 : parse_token_details (void *cls,
552 : json_t *root,
553 : struct GNUNET_JSON_Specification *ospec)
554 : {
555 9 : struct TALER_MERCHANT_ContractTokenFamily *family = ospec->ptr;
556 : const char *class;
557 : const char *ename;
558 : unsigned int eline;
559 : struct GNUNET_JSON_Specification ispec[] = {
560 9 : GNUNET_JSON_spec_string ("class",
561 : &class),
562 9 : GNUNET_JSON_spec_end ()
563 : };
564 :
565 : (void) cls;
566 9 : if (GNUNET_OK !=
567 9 : GNUNET_JSON_parse (root,
568 : ispec,
569 : &ename,
570 : &eline))
571 : {
572 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
573 : "Failed to parse %s at %u: %s\n",
574 : ispec[eline].field,
575 : eline,
576 : ename);
577 0 : GNUNET_break_op (0);
578 0 : return GNUNET_SYSERR;
579 : }
580 :
581 9 : family->kind = contract_token_kind_from_string (class);
582 :
583 9 : switch (family->kind)
584 : {
585 0 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
586 0 : break;
587 7 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
588 : {
589 : const json_t *trusted_domains;
590 : struct GNUNET_JSON_Specification spec[] = {
591 7 : GNUNET_JSON_spec_array_const ("trusted_domains",
592 : &trusted_domains),
593 7 : GNUNET_JSON_spec_end ()
594 : };
595 :
596 7 : if (GNUNET_OK !=
597 7 : GNUNET_JSON_parse (root,
598 : spec,
599 : &ename,
600 : &eline))
601 : {
602 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
603 : "Failed to parse %s at %u: %s\n",
604 : spec[eline].field,
605 : eline,
606 : ename);
607 0 : GNUNET_break_op (0);
608 0 : return GNUNET_SYSERR;
609 : }
610 :
611 7 : GNUNET_array_grow (family->details.subscription.trusted_domains,
612 : family->details.subscription.trusted_domains_len,
613 : json_array_size (trusted_domains));
614 :
615 7 : for (unsigned int i = 0;
616 10 : i < family->details.subscription.trusted_domains_len;
617 3 : i++)
618 : {
619 : const json_t *jdomain;
620 3 : jdomain = json_array_get (trusted_domains, i);
621 :
622 3 : if (! json_is_string (jdomain))
623 : {
624 0 : GNUNET_break_op (0);
625 0 : return GNUNET_SYSERR;
626 : }
627 :
628 3 : family->details.subscription.trusted_domains[i] =
629 3 : GNUNET_strdup (json_string_value (jdomain));
630 : }
631 :
632 7 : return GNUNET_OK;
633 : }
634 2 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
635 : {
636 : const json_t *expected_domains;
637 : struct GNUNET_JSON_Specification spec[] = {
638 2 : GNUNET_JSON_spec_array_const ("expected_domains",
639 : &expected_domains),
640 2 : GNUNET_JSON_spec_end ()
641 : };
642 :
643 2 : if (GNUNET_OK !=
644 2 : GNUNET_JSON_parse (root,
645 : spec,
646 : &ename,
647 : &eline))
648 : {
649 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
650 : "Failed to parse %s at %u: %s\n",
651 : spec[eline].field,
652 : eline,
653 : ename);
654 0 : GNUNET_break_op (0);
655 0 : return GNUNET_SYSERR;
656 : }
657 :
658 2 : GNUNET_array_grow (family->details.discount.expected_domains,
659 : family->details.discount.expected_domains_len,
660 : json_array_size (expected_domains));
661 :
662 2 : for (unsigned int i = 0;
663 5 : i < family->details.discount.expected_domains_len;
664 3 : i++)
665 : {
666 : const json_t *jdomain;
667 :
668 3 : jdomain = json_array_get (expected_domains,
669 : i);
670 3 : if (! json_is_string (jdomain))
671 : {
672 0 : GNUNET_break_op (0);
673 0 : return GNUNET_SYSERR;
674 : }
675 :
676 3 : family->details.discount.expected_domains[i] =
677 3 : GNUNET_strdup (json_string_value (jdomain));
678 : }
679 :
680 2 : return GNUNET_OK;
681 : }
682 : }
683 :
684 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
685 : "Field 'type' invalid in token family\n");
686 0 : GNUNET_break_op (0);
687 0 : return GNUNET_SYSERR;
688 : }
689 :
690 :
691 : /**
692 : * Provide specification to parse given JSON object to contract token details
693 : * in the contract token family. All fields from @a family are copied.
694 : *
695 : * @param name name of the token details field in the JSON
696 : * @param[out] token family where the token details have to be written
697 : */
698 : static struct GNUNET_JSON_Specification
699 9 : spec_token_details (const char *name,
700 : struct TALER_MERCHANT_ContractTokenFamily *family)
701 : {
702 9 : struct GNUNET_JSON_Specification ret = {
703 : .parser = &parse_token_details,
704 : .field = name,
705 : .ptr = family,
706 : };
707 :
708 9 : return ret;
709 : }
710 :
711 :
712 : /**
713 : * Parse given JSON object to token families array.
714 : *
715 : * @param cls closure, pointer to array length
716 : * @param root the json object representing the token families. The keys are
717 : * the token family slugs.
718 : * @param[out] ospec where to write the data
719 : * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
720 : */
721 : static enum GNUNET_GenericReturnValue
722 8 : parse_token_families (void *cls,
723 : json_t *root,
724 : struct GNUNET_JSON_Specification *ospec)
725 : {
726 8 : struct TALER_MERCHANT_ContractTokenFamily **families = ospec->ptr;
727 8 : unsigned int *families_len = cls;
728 : json_t *jfamily;
729 : const char *slug;
730 :
731 8 : if (! json_is_object (root))
732 : {
733 0 : GNUNET_break_op (0);
734 0 : return GNUNET_SYSERR;
735 : }
736 :
737 17 : json_object_foreach (root, slug, jfamily)
738 : {
739 : const json_t *keys;
740 18 : struct TALER_MERCHANT_ContractTokenFamily family = {
741 9 : .slug = GNUNET_strdup (slug)
742 : };
743 : struct GNUNET_JSON_Specification spec[] = {
744 9 : GNUNET_JSON_spec_string_copy ("name",
745 : &family.name),
746 9 : GNUNET_JSON_spec_string_copy ("description",
747 : &family.description),
748 9 : GNUNET_JSON_spec_object_copy ("description_i18n",
749 : &family.description_i18n),
750 9 : GNUNET_JSON_spec_array_const ("keys",
751 : &keys),
752 9 : spec_token_details ("details",
753 : &family),
754 9 : GNUNET_JSON_spec_bool ("critical",
755 : &family.critical),
756 9 : GNUNET_JSON_spec_end ()
757 : };
758 : const char *error_name;
759 : unsigned int error_line;
760 :
761 9 : if (GNUNET_OK !=
762 9 : GNUNET_JSON_parse (jfamily,
763 : spec,
764 : &error_name,
765 : &error_line))
766 : {
767 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
768 : "Failed to parse %s at %u: %s\n",
769 : spec[error_line].field,
770 : error_line,
771 : error_name);
772 0 : GNUNET_break_op (0);
773 0 : return GNUNET_SYSERR;
774 : }
775 :
776 9 : GNUNET_array_grow (family.keys,
777 : family.keys_len,
778 : json_array_size (keys));
779 :
780 17 : for (unsigned int i = 0; i<family.keys_len; i++)
781 : {
782 8 : struct TALER_MERCHANT_ContractTokenFamilyKey *key = &family.keys[i];
783 : struct GNUNET_JSON_Specification key_spec[] = {
784 8 : TALER_JSON_spec_token_pub (
785 : NULL,
786 : &key->pub),
787 8 : GNUNET_JSON_spec_timestamp (
788 : "signature_validity_start",
789 : &key->valid_after),
790 8 : GNUNET_JSON_spec_timestamp (
791 : "signature_validity_end",
792 : &key->valid_before),
793 8 : GNUNET_JSON_spec_end ()
794 : };
795 : const char *ierror_name;
796 : unsigned int ierror_line;
797 :
798 8 : if (GNUNET_OK !=
799 8 : GNUNET_JSON_parse (json_array_get (keys,
800 : i),
801 : key_spec,
802 : &ierror_name,
803 : &ierror_line))
804 : {
805 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
806 : "Failed to parse %s at %u: %s\n",
807 : key_spec[ierror_line].field,
808 : ierror_line,
809 : ierror_name);
810 0 : GNUNET_break_op (0);
811 0 : return GNUNET_SYSERR;
812 : }
813 : }
814 :
815 9 : GNUNET_array_append (*families,
816 : *families_len,
817 : family);
818 : }
819 :
820 8 : return GNUNET_OK;
821 : }
822 :
823 :
824 : /**
825 : * Provide specification to parse given JSON array to token families in the
826 : * contract terms. All fields from @a families items are copied.
827 : *
828 : * @param name name of the token families field in the JSON
829 : * @param[out] families where the token families array has to be written
830 : * @param[out] families_len length of the @a families array
831 : */
832 : static struct GNUNET_JSON_Specification
833 8 : spec_token_families (
834 : const char *name,
835 : struct TALER_MERCHANT_ContractTokenFamily **families,
836 : unsigned int *families_len)
837 : {
838 8 : struct GNUNET_JSON_Specification ret = {
839 : .cls = (void *) families_len,
840 : .parser = &parse_token_families,
841 : .field = name,
842 : .ptr = families,
843 : };
844 :
845 8 : return ret;
846 : }
847 :
848 :
849 : enum GNUNET_GenericReturnValue
850 2 : TALER_MERCHANT_find_token_family_key (
851 : const char *slug,
852 : struct GNUNET_TIME_Timestamp valid_after,
853 : const struct TALER_MERCHANT_ContractTokenFamily *families,
854 : unsigned int families_len,
855 : struct TALER_MERCHANT_ContractTokenFamily *family,
856 : struct TALER_MERCHANT_ContractTokenFamilyKey *key)
857 : {
858 3 : for (unsigned int i = 0; i < families_len; i++)
859 : {
860 3 : const struct TALER_MERCHANT_ContractTokenFamily *fami
861 3 : = &families[i];
862 :
863 3 : if (0 != strcmp (fami->slug,
864 : slug))
865 1 : continue;
866 2 : if (NULL != family)
867 2 : *family = *fami;
868 2 : for (unsigned int k = 0; k < fami->keys_len; k++)
869 : {
870 2 : struct TALER_MERCHANT_ContractTokenFamilyKey *ki = &fami->keys[k];
871 :
872 2 : if (GNUNET_TIME_timestamp_cmp (ki->valid_after,
873 : ==,
874 : valid_after))
875 : {
876 2 : if (NULL != key)
877 2 : *key = *ki;
878 2 : return GNUNET_OK;
879 : }
880 : }
881 : /* matching family found, but no key. */
882 0 : return GNUNET_NO;
883 : }
884 :
885 : /* no matching family found */
886 0 : return GNUNET_SYSERR;
887 : }
888 :
889 :
890 : /**
891 : * Free all the fields in the given @a family, but not @a family itself, since
892 : * it is normally part of an array.
893 : *
894 : * @param[in] family contract token family to free
895 : */
896 : static void
897 9 : contract_token_family_free (
898 : struct TALER_MERCHANT_ContractTokenFamily *family)
899 : {
900 9 : GNUNET_free (family->slug);
901 9 : GNUNET_free (family->name);
902 9 : GNUNET_free (family->description);
903 9 : if (NULL != family->description_i18n)
904 : {
905 9 : json_decref (family->description_i18n);
906 9 : family->description_i18n = NULL;
907 : }
908 17 : for (unsigned int i = 0; i < family->keys_len; i++)
909 8 : TALER_token_issue_pub_free (&family->keys[i].pub);
910 9 : GNUNET_free (family->keys);
911 :
912 9 : switch (family->kind)
913 : {
914 0 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
915 0 : break;
916 2 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
917 5 : for (unsigned int i = 0; i < family->details.discount.expected_domains_len;
918 3 : i++)
919 3 : GNUNET_free (family->details.discount.expected_domains[i]);
920 2 : break;
921 7 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
922 10 : for (unsigned int i = 0; i < family->details.subscription.
923 3 : trusted_domains_len; i++)
924 3 : GNUNET_free (family->details.subscription.trusted_domains[i]);
925 7 : break;
926 : }
927 9 : }
928 :
929 :
930 : /**
931 : * Parse contract version of given JSON contract terms.
932 : *
933 : * @param cls closure, unused parameter
934 : * @param root the JSON object representing data
935 : * @param[out] spec where to write the data
936 : * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
937 : */
938 : static enum GNUNET_GenericReturnValue
939 152 : parse_contract_version (void *cls,
940 : json_t *root,
941 : struct GNUNET_JSON_Specification *spec)
942 : {
943 152 : enum TALER_MERCHANT_ContractVersion *res
944 : = (enum TALER_MERCHANT_ContractVersion *) spec->ptr;
945 :
946 : (void) cls;
947 152 : if (json_is_integer (root))
948 : {
949 152 : json_int_t version = json_integer_value (root);
950 :
951 152 : switch (version)
952 : {
953 144 : case 0:
954 144 : *res = TALER_MERCHANT_CONTRACT_VERSION_0;
955 144 : return GNUNET_OK;
956 8 : case 1:
957 8 : *res = TALER_MERCHANT_CONTRACT_VERSION_1;
958 8 : return GNUNET_OK;
959 : }
960 :
961 0 : GNUNET_break_op (0);
962 0 : return GNUNET_SYSERR;
963 : }
964 :
965 0 : if (json_is_null (root))
966 : {
967 0 : *res = TALER_MERCHANT_CONTRACT_VERSION_0;
968 0 : return GNUNET_OK;
969 : }
970 :
971 0 : GNUNET_break_op (0);
972 0 : return GNUNET_SYSERR;
973 : }
974 :
975 :
976 : /**
977 : * Create JSON specification to parse a merchant contract
978 : * version.
979 : *
980 : * @param name name of the field
981 : * @param[out] version where to write the contract version
982 : * @return JSON specification object
983 : */
984 : static struct GNUNET_JSON_Specification
985 152 : spec_contract_version (
986 : const char *name,
987 : enum TALER_MERCHANT_ContractVersion *version)
988 : {
989 152 : struct GNUNET_JSON_Specification ret = {
990 : .parser = &parse_contract_version,
991 : .field = name,
992 : .ptr = version
993 : };
994 :
995 152 : *version = TALER_MERCHANT_CONTRACT_VERSION_0;
996 152 : return ret;
997 : }
998 :
999 :
1000 : /**
1001 : * Parse v0-specific fields of @a input JSON into @a contract.
1002 : *
1003 : * @param[in] input the JSON contract terms
1004 : * @param[out] contract where to write the data
1005 : * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
1006 : */
1007 : static enum GNUNET_GenericReturnValue
1008 144 : parse_contract_v0 (
1009 : json_t *input,
1010 : struct TALER_MERCHANT_Contract *contract)
1011 : {
1012 : struct GNUNET_JSON_Specification espec[] = {
1013 144 : TALER_JSON_spec_amount_any ("amount",
1014 : &contract->details.v0.brutto),
1015 144 : TALER_JSON_spec_amount_any ("max_fee",
1016 : &contract->details.v0.max_fee),
1017 144 : GNUNET_JSON_spec_end ()
1018 : };
1019 : enum GNUNET_GenericReturnValue res;
1020 : const char *ename;
1021 : unsigned int eline;
1022 :
1023 144 : res = GNUNET_JSON_parse (input,
1024 : espec,
1025 : &ename,
1026 : &eline);
1027 144 : if (GNUNET_OK != res)
1028 : {
1029 0 : GNUNET_break (0);
1030 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1031 : "Failed to parse contract v0 at field %s\n",
1032 : ename);
1033 0 : return GNUNET_SYSERR;
1034 : }
1035 :
1036 144 : if (GNUNET_OK !=
1037 144 : TALER_amount_cmp_currency (&contract->details.v0.max_fee,
1038 144 : &contract->details.v0.brutto))
1039 : {
1040 0 : GNUNET_break (0);
1041 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1042 : "'max_fee' in database does not match currency of contract price");
1043 0 : return GNUNET_SYSERR;
1044 : }
1045 :
1046 144 : return res;
1047 : }
1048 :
1049 :
1050 : /**
1051 : * Parse v1-specific fields of @a input JSON into @a contract.
1052 : *
1053 : * @param[in] input the JSON contract terms
1054 : * @param[out] contract where to write the data
1055 : * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
1056 : */
1057 : static enum GNUNET_GenericReturnValue
1058 8 : parse_contract_v1 (
1059 : json_t *input,
1060 : struct TALER_MERCHANT_Contract *contract)
1061 : {
1062 : struct GNUNET_JSON_Specification espec[] = {
1063 8 : spec_choices ("choices",
1064 : &contract->details.v1.choices,
1065 : &contract->details.v1.choices_len),
1066 8 : spec_token_families ("token_families",
1067 : &contract->details.v1.token_authorities,
1068 : &contract->details.v1.
1069 : token_authorities_len),
1070 8 : GNUNET_JSON_spec_end ()
1071 : };
1072 :
1073 : enum GNUNET_GenericReturnValue res;
1074 : const char *ename;
1075 : unsigned int eline;
1076 :
1077 8 : res = GNUNET_JSON_parse (input,
1078 : espec,
1079 : &ename,
1080 : &eline);
1081 :
1082 8 : if (GNUNET_OK != res)
1083 : {
1084 0 : GNUNET_break (0);
1085 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1086 : "Failed to parse contract v1 at field %s\n",
1087 : ename);
1088 0 : return GNUNET_SYSERR;
1089 : }
1090 :
1091 8 : return res;
1092 : }
1093 :
1094 :
1095 : struct TALER_MERCHANT_Contract *
1096 152 : TALER_MERCHANT_contract_parse (json_t *input,
1097 : bool nonce_optional)
1098 : {
1099 : struct TALER_MERCHANT_Contract *contract
1100 152 : = GNUNET_new (struct TALER_MERCHANT_Contract);
1101 : struct GNUNET_JSON_Specification espec[] = {
1102 152 : spec_contract_version ("version",
1103 : &contract->version),
1104 152 : GNUNET_JSON_spec_string_copy ("summary",
1105 : &contract->summary),
1106 : /* FIXME: do i18n_str validation in the future */
1107 152 : GNUNET_JSON_spec_mark_optional (
1108 : GNUNET_JSON_spec_object_copy ("summary_i18n",
1109 : &contract->summary_i18n),
1110 : NULL),
1111 152 : GNUNET_JSON_spec_string_copy ("order_id",
1112 : &contract->order_id),
1113 152 : GNUNET_JSON_spec_mark_optional (
1114 : GNUNET_JSON_spec_string_copy ("public_reorder_url",
1115 : &contract->public_reorder_url),
1116 : NULL),
1117 152 : GNUNET_JSON_spec_mark_optional (
1118 : GNUNET_JSON_spec_string_copy ("fulfillment_url",
1119 : &contract->fulfillment_url),
1120 : NULL),
1121 152 : GNUNET_JSON_spec_mark_optional (
1122 : GNUNET_JSON_spec_string_copy ("fulfillment_message",
1123 : &contract->fulfillment_message),
1124 : NULL),
1125 152 : GNUNET_JSON_spec_mark_optional (
1126 : GNUNET_JSON_spec_object_copy ("fulfillment_message_i18n",
1127 : &contract->fulfillment_message_i18n),
1128 : NULL),
1129 152 : GNUNET_JSON_spec_array_copy ("products",
1130 : &contract->products),
1131 152 : GNUNET_JSON_spec_timestamp ("timestamp",
1132 : &contract->timestamp),
1133 152 : GNUNET_JSON_spec_timestamp ("refund_deadline",
1134 : &contract->refund_deadline),
1135 152 : GNUNET_JSON_spec_timestamp ("pay_deadline",
1136 : &contract->pay_deadline),
1137 152 : GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
1138 : &contract->wire_deadline),
1139 152 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
1140 : &contract->merchant_pub),
1141 152 : GNUNET_JSON_spec_string_copy ("merchant_base_url",
1142 : &contract->merchant_base_url),
1143 152 : spec_merchant_details ("merchant",
1144 : contract),
1145 152 : GNUNET_JSON_spec_fixed_auto ("h_wire",
1146 : &contract->h_wire),
1147 152 : GNUNET_JSON_spec_string_copy ("wire_method",
1148 : &contract->wire_method),
1149 152 : GNUNET_JSON_spec_array_copy ("exchanges",
1150 : &contract->exchanges),
1151 152 : GNUNET_JSON_spec_mark_optional (
1152 : GNUNET_JSON_spec_object_copy ("delivery_location",
1153 : &contract->delivery_location),
1154 : NULL),
1155 152 : GNUNET_JSON_spec_mark_optional (
1156 : GNUNET_JSON_spec_timestamp ("delivery_date",
1157 : &contract->delivery_date),
1158 : NULL),
1159 : (nonce_optional)
1160 150 : ? GNUNET_JSON_spec_mark_optional (
1161 : GNUNET_JSON_spec_string_copy ("nonce",
1162 : &contract->nonce),
1163 : NULL)
1164 152 : : GNUNET_JSON_spec_string_copy ("nonce",
1165 : &contract->nonce),
1166 152 : GNUNET_JSON_spec_mark_optional (
1167 : GNUNET_JSON_spec_relative_time ("auto_refund",
1168 : &contract->auto_refund),
1169 : NULL),
1170 152 : GNUNET_JSON_spec_mark_optional (
1171 : GNUNET_JSON_spec_object_copy ("extra",
1172 : &contract->extra),
1173 : NULL),
1174 152 : GNUNET_JSON_spec_mark_optional (
1175 : GNUNET_JSON_spec_uint8 ("minimum_age",
1176 : &contract->minimum_age),
1177 : NULL),
1178 152 : GNUNET_JSON_spec_end ()
1179 : };
1180 :
1181 : enum GNUNET_GenericReturnValue res;
1182 : const char *ename;
1183 : unsigned int eline;
1184 :
1185 152 : GNUNET_assert (NULL != input);
1186 152 : res = GNUNET_JSON_parse (input,
1187 : espec,
1188 : &ename,
1189 : &eline);
1190 152 : if (GNUNET_OK != res)
1191 : {
1192 0 : GNUNET_break (0);
1193 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1194 : "Failed to parse contract at field %s\n",
1195 : ename);
1196 0 : goto cleanup;
1197 : }
1198 :
1199 152 : switch (contract->version)
1200 : {
1201 144 : case TALER_MERCHANT_CONTRACT_VERSION_0:
1202 144 : res = parse_contract_v0 (input,
1203 : contract);
1204 144 : break;
1205 8 : case TALER_MERCHANT_CONTRACT_VERSION_1:
1206 8 : res = parse_contract_v1 (input,
1207 : contract);
1208 8 : break;
1209 : }
1210 :
1211 152 : if (GNUNET_OK != res)
1212 : {
1213 0 : GNUNET_break (0);
1214 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1215 : "Failed to parse contract\n");
1216 0 : goto cleanup;
1217 : }
1218 152 : return contract;
1219 :
1220 0 : cleanup:
1221 0 : TALER_MERCHANT_contract_free (contract);
1222 0 : return NULL;
1223 : }
1224 :
1225 :
1226 : void
1227 152 : TALER_MERCHANT_contract_free (
1228 : struct TALER_MERCHANT_Contract *contract)
1229 : {
1230 152 : if (NULL == contract)
1231 0 : return;
1232 152 : GNUNET_free (contract->public_reorder_url);
1233 152 : GNUNET_free (contract->order_id);
1234 152 : GNUNET_free (contract->merchant_base_url);
1235 152 : GNUNET_free (contract->merchant.name);
1236 152 : GNUNET_free (contract->merchant.website);
1237 152 : GNUNET_free (contract->merchant.email);
1238 152 : GNUNET_free (contract->merchant.logo);
1239 152 : if (NULL != contract->merchant.address)
1240 : {
1241 152 : json_decref (contract->merchant.address);
1242 152 : contract->merchant.address = NULL;
1243 : }
1244 152 : if (NULL != contract->merchant.jurisdiction)
1245 : {
1246 152 : json_decref (contract->merchant.jurisdiction);
1247 152 : contract->merchant.jurisdiction = NULL;
1248 : }
1249 152 : GNUNET_free (contract->summary);
1250 152 : GNUNET_free (contract->fulfillment_url);
1251 152 : GNUNET_free (contract->fulfillment_message);
1252 152 : if (NULL != contract->fulfillment_message_i18n)
1253 : {
1254 2 : json_decref (contract->fulfillment_message_i18n);
1255 2 : contract->fulfillment_message_i18n = NULL;
1256 : }
1257 152 : if (NULL != contract->products)
1258 : {
1259 152 : json_decref (contract->products);
1260 152 : contract->products = NULL;
1261 : }
1262 152 : GNUNET_free (contract->wire_method);
1263 152 : if (NULL != contract->exchanges)
1264 : {
1265 152 : json_decref (contract->exchanges);
1266 152 : contract->exchanges = NULL;
1267 : }
1268 152 : if (NULL != contract->delivery_location)
1269 : {
1270 2 : json_decref (contract->delivery_location);
1271 2 : contract->delivery_location = NULL;
1272 : }
1273 152 : GNUNET_free (contract->nonce);
1274 152 : if (NULL != contract->extra)
1275 : {
1276 2 : json_decref (contract->extra);
1277 2 : contract->extra = NULL;
1278 : }
1279 :
1280 152 : switch (contract->version)
1281 : {
1282 144 : case TALER_MERCHANT_CONTRACT_VERSION_0:
1283 144 : break;
1284 8 : case TALER_MERCHANT_CONTRACT_VERSION_1:
1285 8 : for (unsigned int i = 0;
1286 17 : i < contract->details.v1.choices_len;
1287 9 : i++)
1288 9 : contract_choice_free (&contract->details.v1.choices[i]);
1289 8 : GNUNET_free (contract->details.v1.choices);
1290 8 : for (unsigned int i = 0;
1291 17 : i < contract->details.v1.token_authorities_len;
1292 9 : i++)
1293 9 : contract_token_family_free (&contract->details.v1.token_authorities[i]);
1294 8 : GNUNET_free (contract->details.v1.token_authorities);
1295 8 : break;
1296 : }
1297 152 : GNUNET_free (contract);
1298 : }
|