Line data Source code
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 51 : TALER_MERCHANT_template_type_from_contract (const json_t *template_contract)
34 : {
35 : const json_t *type_val;
36 :
37 51 : if (NULL == template_contract)
38 0 : return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
39 :
40 51 : type_val = json_object_get (template_contract,
41 : "template_type");
42 :
43 51 : if (NULL == type_val)
44 : {
45 : /* missing => fixed order */
46 18 : return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
47 : }
48 33 : if (! json_is_string (type_val))
49 : {
50 0 : GNUNET_break_op (0);
51 0 : return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
52 : }
53 :
54 33 : return TALER_MERCHANT_template_type_from_string (
55 : json_string_value (type_val));
56 : }
57 :
58 :
59 : enum TALER_MERCHANT_TemplateType
60 165 : TALER_MERCHANT_template_type_from_string (const char *template_type)
61 : {
62 165 : if (NULL == template_type)
63 49 : return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
64 116 : if (0 == strcmp (template_type,
65 : "fixed-order"))
66 18 : return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
67 98 : if (0 == strcmp (template_type,
68 : "inventory-cart"))
69 72 : return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
70 26 : if (0 == strcmp (template_type,
71 : "paivana"))
72 26 : return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
73 0 : return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
74 : }
75 :
76 :
77 : const char *
78 0 : TALER_MERCHANT_template_type_to_string (
79 : enum TALER_MERCHANT_TemplateType template_type)
80 : {
81 0 : switch (template_type)
82 : {
83 0 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
84 0 : return "fixed-order";
85 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
86 0 : return "inventory-cart";
87 0 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
88 0 : return "paivana";
89 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
90 0 : break;
91 : }
92 0 : 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 28 : 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 28 : GNUNET_JSON_spec_mark_optional (
111 : GNUNET_JSON_spec_bool (
112 : "selected_all",
113 : &out->details.inventory.selected_all),
114 : NULL),
115 28 : GNUNET_JSON_spec_mark_optional (
116 : GNUNET_JSON_spec_array_const (
117 : "selected_categories",
118 : &out->details.inventory.selected_categories)
119 : ,
120 : NULL),
121 28 : GNUNET_JSON_spec_mark_optional (
122 : GNUNET_JSON_spec_array_const (
123 : "selected_products",
124 : &out->details.inventory.selected_products),
125 : NULL),
126 28 : GNUNET_JSON_spec_mark_optional (
127 : GNUNET_JSON_spec_bool (
128 : "choose_one",
129 : &out->details.inventory.choose_one),
130 : NULL),
131 28 : GNUNET_JSON_spec_end ()
132 : };
133 : const char *en;
134 :
135 28 : if (GNUNET_OK !=
136 28 : GNUNET_JSON_parse ((json_t *) template_contract,
137 : spec,
138 : &en,
139 : NULL))
140 : {
141 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
142 : "Invalid inventory template_contract for field %s\n",
143 : en);
144 0 : if (NULL != error_name)
145 0 : *error_name = en;
146 0 : return GNUNET_SYSERR;
147 : }
148 :
149 28 : if (NULL != out->details.inventory.selected_categories)
150 : {
151 : const json_t *entry;
152 : size_t idx;
153 :
154 8 : json_array_foreach ((json_t *) out->details.inventory.selected_categories,
155 : idx,
156 : entry)
157 : {
158 8 : if ( (! json_is_integer (entry)) ||
159 4 : (0 > json_integer_value (entry)) )
160 : {
161 0 : GNUNET_break_op (0);
162 0 : if (NULL != error_name)
163 0 : *error_name = "selected_categories";
164 0 : return GNUNET_SYSERR;
165 : }
166 : }
167 : }
168 :
169 28 : if (NULL != out->details.inventory.selected_products)
170 : {
171 : const json_t *entry;
172 : size_t idx;
173 :
174 80 : json_array_foreach ((json_t *) out->details.inventory.selected_products,
175 : idx,
176 : entry)
177 : {
178 52 : if (! json_is_string (entry))
179 : {
180 0 : GNUNET_break_op (0);
181 0 : if (NULL != error_name)
182 0 : *error_name = "selected_products";
183 0 : return GNUNET_SYSERR;
184 : }
185 : }
186 : }
187 28 : 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 9 : 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 9 : GNUNET_JSON_spec_mark_optional (
206 : GNUNET_JSON_spec_string ("website_regex",
207 : &out->details.paivana.website_regex),
208 : NULL),
209 9 : TALER_MERCHANT_spec_order_choices ("choices",
210 : &out->details.paivana.choices,
211 : &out->details.paivana.choices_len),
212 9 : GNUNET_JSON_spec_end ()
213 : };
214 : const char *en;
215 :
216 9 : if (GNUNET_OK !=
217 9 : GNUNET_JSON_parse ((json_t *) template_contract,
218 : spec,
219 : &en,
220 : NULL))
221 : {
222 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
223 : "Invalid paivana template_contract for field %s\n",
224 : en);
225 0 : if (NULL != error_name)
226 0 : *error_name = en;
227 0 : return GNUNET_SYSERR;
228 : }
229 9 : if (NULL != out->details.paivana.website_regex)
230 : {
231 : regex_t ex;
232 :
233 0 : if (0 != regcomp (&ex,
234 : out->details.paivana.website_regex,
235 : REG_NOSUB | REG_EXTENDED))
236 : {
237 0 : GNUNET_break_op (0);
238 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
239 : "Invalid paivana website_regex given\n");
240 0 : if (NULL != error_name)
241 0 : *error_name = "Invalid website_regex given";
242 0 : return GNUNET_SYSERR;
243 : }
244 0 : regfree (&ex);
245 : }
246 9 : 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 89 : check_amount_limits (const struct TALER_MERCHANT_TemplateContract *tc,
263 : const char **error_name)
264 : {
265 89 : if (tc->no_min_amount &&
266 80 : tc->no_max_amount)
267 80 : return GNUNET_OK;
268 9 : if ( (! tc->no_min_amount) &&
269 9 : (! 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 8 : if (GNUNET_YES !=
276 8 : TALER_amount_cmp_currency (&tc->min_amount,
277 : &tc->max_amount))
278 : {
279 1 : GNUNET_break_op (0);
280 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
281 : "min_amount and max_amount must use the same currency\n");
282 1 : if (NULL != error_name)
283 0 : *error_name = "max_amount";
284 1 : return GNUNET_SYSERR;
285 : }
286 7 : if (0 < TALER_amount_cmp (&tc->min_amount,
287 : &tc->max_amount))
288 : {
289 1 : GNUNET_break_op (0);
290 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
291 : "min_amount exceeds max_amount\n");
292 1 : if (NULL != error_name)
293 0 : *error_name = "min_amount";
294 1 : return GNUNET_SYSERR;
295 : }
296 : }
297 7 : if (NULL == tc->currency)
298 : {
299 1 : GNUNET_break_op (0);
300 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
301 : "Template with min_amount or max_amount must specify a currency\n");
302 1 : if (NULL != error_name)
303 0 : *error_name = "currency";
304 1 : return GNUNET_SYSERR;
305 : }
306 6 : if ( (! tc->no_min_amount) &&
307 6 : (0 != strcasecmp (tc->currency,
308 6 : tc->min_amount.currency)) )
309 : {
310 0 : GNUNET_break_op (0);
311 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
312 : "min_amount must use the currency of the template (%s)\n",
313 : tc->currency);
314 0 : if (NULL != error_name)
315 0 : *error_name = "min_amount";
316 0 : return GNUNET_SYSERR;
317 : }
318 6 : if ( (! tc->no_max_amount) &&
319 6 : (0 != strcasecmp (tc->currency,
320 6 : tc->max_amount.currency)) )
321 : {
322 0 : GNUNET_break_op (0);
323 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
324 : "max_amount must use the currency of the template (%s)\n",
325 : tc->currency);
326 0 : if (NULL != error_name)
327 0 : *error_name = "max_amount";
328 0 : return GNUNET_SYSERR;
329 : }
330 6 : return GNUNET_OK;
331 : }
332 :
333 :
334 : enum GNUNET_GenericReturnValue
335 89 : TALER_MERCHANT_template_contract_parse (
336 : const json_t *template_contract,
337 : struct TALER_MERCHANT_TemplateContract *out,
338 : const char **error_name)
339 : {
340 89 : const char *template_type_str = NULL;
341 : struct GNUNET_JSON_Specification spec[] = {
342 89 : GNUNET_JSON_spec_mark_optional (
343 : GNUNET_JSON_spec_string ("template_type",
344 : &template_type_str),
345 : NULL),
346 89 : GNUNET_JSON_spec_mark_optional (
347 : GNUNET_JSON_spec_string ("summary",
348 : &out->summary),
349 : NULL),
350 89 : GNUNET_JSON_spec_mark_optional (
351 : GNUNET_JSON_spec_string ("currency",
352 : &out->currency),
353 : NULL),
354 89 : GNUNET_JSON_spec_mark_optional (
355 : TALER_JSON_spec_amount_any ("amount",
356 : &out->amount),
357 : &out->no_amount),
358 89 : GNUNET_JSON_spec_mark_optional (
359 : TALER_JSON_spec_amount_any ("min_amount",
360 : &out->min_amount),
361 : &out->no_min_amount),
362 89 : GNUNET_JSON_spec_mark_optional (
363 : TALER_JSON_spec_amount_any ("max_amount",
364 : &out->max_amount),
365 : &out->no_max_amount),
366 89 : GNUNET_JSON_spec_mark_optional (
367 : GNUNET_JSON_spec_uint32 ("minimum_age",
368 : &out->minimum_age),
369 : NULL),
370 89 : GNUNET_JSON_spec_mark_optional (
371 : GNUNET_JSON_spec_relative_time ("pay_duration",
372 : &out->pay_duration),
373 : NULL),
374 89 : GNUNET_JSON_spec_mark_optional (
375 : GNUNET_JSON_spec_relative_time ("max_pickup_duration",
376 : &out->max_pickup_duration),
377 : NULL),
378 89 : GNUNET_JSON_spec_mark_optional (
379 : GNUNET_JSON_spec_bool ("request_tip",
380 : &out->request_tip),
381 : NULL),
382 89 : GNUNET_JSON_spec_end ()
383 : };
384 : const char *en;
385 :
386 89 : if (NULL == template_contract)
387 : {
388 0 : if (NULL != error_name)
389 0 : *error_name = "template_contract is NULL";
390 0 : return GNUNET_SYSERR;
391 : }
392 89 : out->max_pickup_duration = GNUNET_TIME_UNIT_FOREVER_REL;
393 89 : out->no_min_amount = true;
394 89 : out->no_max_amount = true;
395 89 : if (GNUNET_OK !=
396 89 : GNUNET_JSON_parse ((json_t *) template_contract,
397 : spec,
398 : &en,
399 : NULL))
400 : {
401 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
402 : "Invalid input for field %s\n",
403 : en);
404 0 : if (NULL != error_name)
405 0 : *error_name = en;
406 0 : return GNUNET_SYSERR;
407 : }
408 89 : if (GNUNET_OK !=
409 89 : check_amount_limits (out,
410 : error_name))
411 3 : return GNUNET_SYSERR;
412 :
413 86 : out->type = TALER_MERCHANT_template_type_from_string (template_type_str);
414 86 : if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type)
415 : {
416 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
417 : "Invalid template_type used '%s'\n",
418 : template_type_str);
419 0 : if (NULL != error_name)
420 0 : *error_name = "Invalid template_type used";
421 0 : return GNUNET_SYSERR;
422 : }
423 :
424 : /* Parse additional fields for each specific type */
425 86 : switch (out->type)
426 : {
427 49 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
428 49 : return GNUNET_OK;
429 28 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
430 28 : return parse_template_inventory (template_contract,
431 : out,
432 : error_name);
433 9 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
434 9 : return parse_template_paivana (template_contract,
435 : out,
436 : error_name);
437 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
438 0 : break;
439 : }
440 :
441 : /* I think we are never supposed to reach it */
442 0 : GNUNET_break_op (0);
443 0 : if (NULL != error_name)
444 0 : *error_name = "template_type";
445 0 : return GNUNET_SYSERR;
446 : }
447 :
448 :
449 : void
450 92 : TALER_MERCHANT_template_contract_free (
451 : struct TALER_MERCHANT_TemplateContract *tc)
452 : {
453 92 : switch (tc->type)
454 : {
455 49 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
456 49 : return;
457 28 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
458 28 : return;
459 9 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
460 18 : for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++)
461 9 : TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]);
462 9 : GNUNET_array_grow (tc->details.paivana.choices,
463 : tc->details.paivana.choices_len,
464 : 0);
465 9 : return;
466 6 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
467 6 : return;
468 : }
469 : }
470 :
471 :
472 : bool
473 45 : TALER_MERCHANT_template_contract_valid (const json_t *template_contract)
474 : {
475 : struct TALER_MERCHANT_TemplateContract tmp;
476 : bool ret;
477 :
478 45 : memset (&tmp,
479 : 0,
480 : sizeof (tmp));
481 45 : ret = (GNUNET_OK ==
482 45 : TALER_MERCHANT_template_contract_parse (template_contract,
483 : &tmp,
484 : NULL));
485 87 : if (ret &&
486 42 : GNUNET_TIME_relative_is_forever (tmp.pay_duration))
487 5 : ret = false;
488 45 : TALER_MERCHANT_template_contract_free (&tmp);
489 45 : return ret;
490 : }
|