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