Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2023 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU 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.c
18 : * @brief Common utility functions
19 : * @author Sree Harsha Totakura <sreeharsha@totakura.in>
20 : * @author Florian Dold
21 : * @author Benedikt Mueller
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : #include "taler/taler_util.h"
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <unistr.h>
28 :
29 :
30 : int
31 0 : TALER_strcmp_ct (const char *a,
32 : const char *b)
33 : {
34 0 : size_t alen = strlen (a);
35 :
36 0 : if (strlen (b) != alen)
37 0 : return 1;
38 0 : return GNUNET_memcmp_ct_ (a,
39 : b,
40 : alen);
41 : }
42 :
43 :
44 : const char *
45 2224 : TALER_b2s (const void *buf,
46 : size_t buf_size)
47 : {
48 : static TALER_THREAD_LOCAL char ret[9];
49 : struct GNUNET_HashCode hc;
50 : char *tmp;
51 :
52 2224 : GNUNET_CRYPTO_hash (buf,
53 : buf_size,
54 : &hc);
55 2224 : tmp = GNUNET_STRINGS_data_to_string_alloc (&hc,
56 : sizeof (hc));
57 2224 : GNUNET_memcpy (ret,
58 : tmp,
59 : 8);
60 2224 : GNUNET_free (tmp);
61 2224 : ret[8] = '\0';
62 2224 : return ret;
63 : }
64 :
65 :
66 : void
67 3816 : TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
68 : const struct TALER_DenomFeeSet *fees)
69 : {
70 3816 : TALER_amount_hton (&nbo->withdraw,
71 : &fees->withdraw);
72 3816 : TALER_amount_hton (&nbo->deposit,
73 : &fees->deposit);
74 3816 : TALER_amount_hton (&nbo->refresh,
75 : &fees->refresh);
76 3816 : TALER_amount_hton (&nbo->refund,
77 : &fees->refund);
78 3816 : }
79 :
80 :
81 : void
82 0 : TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees,
83 : const struct TALER_DenomFeeSetNBOP *nbo)
84 : {
85 0 : TALER_amount_ntoh (&fees->withdraw,
86 : &nbo->withdraw);
87 0 : TALER_amount_ntoh (&fees->deposit,
88 : &nbo->deposit);
89 0 : TALER_amount_ntoh (&fees->refresh,
90 : &nbo->refresh);
91 0 : TALER_amount_ntoh (&fees->refund,
92 : &nbo->refund);
93 0 : }
94 :
95 :
96 : void
97 182 : TALER_global_fee_set_hton (struct TALER_GlobalFeeSetNBOP *nbo,
98 : const struct TALER_GlobalFeeSet *fees)
99 : {
100 182 : TALER_amount_hton (&nbo->history,
101 : &fees->history);
102 182 : TALER_amount_hton (&nbo->account,
103 : &fees->account);
104 182 : TALER_amount_hton (&nbo->purse,
105 : &fees->purse);
106 182 : }
107 :
108 :
109 : void
110 0 : TALER_global_fee_set_ntoh (struct TALER_GlobalFeeSet *fees,
111 : const struct TALER_GlobalFeeSetNBOP *nbo)
112 : {
113 0 : TALER_amount_ntoh (&fees->history,
114 : &nbo->history);
115 0 : TALER_amount_ntoh (&fees->account,
116 : &nbo->account);
117 0 : TALER_amount_ntoh (&fees->purse,
118 : &nbo->purse);
119 0 : }
120 :
121 :
122 : void
123 208 : TALER_wire_fee_set_hton (struct TALER_WireFeeSetNBOP *nbo,
124 : const struct TALER_WireFeeSet *fees)
125 : {
126 208 : TALER_amount_hton (&nbo->wire,
127 : &fees->wire);
128 208 : TALER_amount_hton (&nbo->closing,
129 : &fees->closing);
130 208 : }
131 :
132 :
133 : void
134 0 : TALER_wire_fee_set_ntoh (struct TALER_WireFeeSet *fees,
135 : const struct TALER_WireFeeSetNBOP *nbo)
136 : {
137 0 : TALER_amount_ntoh (&fees->wire,
138 : &nbo->wire);
139 0 : TALER_amount_ntoh (&fees->closing,
140 : &nbo->closing);
141 0 : }
142 :
143 :
144 : int
145 3 : TALER_global_fee_set_cmp (const struct TALER_GlobalFeeSet *f1,
146 : const struct TALER_GlobalFeeSet *f2)
147 : {
148 : int ret;
149 :
150 3 : ret = TALER_amount_cmp (&f1->history,
151 : &f2->history);
152 3 : if (0 != ret)
153 1 : return ret;
154 2 : ret = TALER_amount_cmp (&f1->account,
155 : &f2->account);
156 2 : if (0 != ret)
157 0 : return ret;
158 2 : ret = TALER_amount_cmp (&f1->purse,
159 : &f2->purse);
160 2 : if (0 != ret)
161 0 : return ret;
162 2 : return 0;
163 : }
164 :
165 :
166 : int
167 7 : TALER_wire_fee_set_cmp (const struct TALER_WireFeeSet *f1,
168 : const struct TALER_WireFeeSet *f2)
169 : {
170 : int ret;
171 :
172 7 : ret = TALER_amount_cmp (&f1->wire,
173 : &f2->wire);
174 7 : if (0 != ret)
175 1 : return ret;
176 6 : ret = TALER_amount_cmp (&f1->closing,
177 : &f2->closing);
178 6 : if (0 != ret)
179 2 : return ret;
180 4 : return 0;
181 : }
182 :
183 :
184 : enum GNUNET_GenericReturnValue
185 1114 : TALER_denom_fee_check_currency (
186 : const char *currency,
187 : const struct TALER_DenomFeeSet *fees)
188 : {
189 1114 : if (GNUNET_YES !=
190 1114 : TALER_amount_is_currency (&fees->withdraw,
191 : currency))
192 : {
193 0 : GNUNET_break (0);
194 0 : return GNUNET_NO;
195 : }
196 1114 : if (GNUNET_YES !=
197 1114 : TALER_amount_is_currency (&fees->deposit,
198 : currency))
199 : {
200 0 : GNUNET_break (0);
201 0 : return GNUNET_NO;
202 : }
203 1114 : if (GNUNET_YES !=
204 1114 : TALER_amount_is_currency (&fees->refresh,
205 : currency))
206 : {
207 0 : GNUNET_break (0);
208 0 : return GNUNET_NO;
209 : }
210 1114 : if (GNUNET_YES !=
211 1114 : TALER_amount_is_currency (&fees->refund,
212 : currency))
213 : {
214 0 : GNUNET_break (0);
215 0 : return GNUNET_NO;
216 : }
217 1114 : return GNUNET_OK;
218 : }
219 :
220 :
221 : /**
222 : * Dump character in the low range into @a buf
223 : * following RFC 8785.
224 : *
225 : * @param[in,out] buf buffer to modify
226 : * @param val value to dump
227 : */
228 : static void
229 3 : lowdump (struct GNUNET_Buffer *buf,
230 : unsigned char val)
231 : {
232 : char scratch[7];
233 :
234 3 : switch (val)
235 : {
236 0 : case 0x8:
237 0 : GNUNET_buffer_write (buf,
238 : "\\b",
239 : 2);
240 0 : break;
241 0 : case 0x9:
242 0 : GNUNET_buffer_write (buf,
243 : "\\t",
244 : 2);
245 0 : break;
246 0 : case 0xA:
247 0 : GNUNET_buffer_write (buf,
248 : "\\n",
249 : 2);
250 0 : break;
251 0 : case 0xC:
252 0 : GNUNET_buffer_write (buf,
253 : "\\f",
254 : 2);
255 0 : break;
256 0 : case 0xD:
257 0 : GNUNET_buffer_write (buf,
258 : "\\r",
259 : 2);
260 0 : break;
261 3 : default:
262 3 : GNUNET_snprintf (scratch,
263 : sizeof (scratch),
264 : "\\u%04x",
265 : (unsigned int) val);
266 3 : GNUNET_buffer_write (buf,
267 : scratch,
268 : 6);
269 3 : break;
270 : }
271 3 : }
272 :
273 :
274 : size_t
275 617 : TALER_rfc8785encode (char **inp)
276 : {
277 617 : struct GNUNET_Buffer buf = { 0 };
278 617 : size_t left = strlen (*inp) + 1;
279 : size_t olen;
280 617 : char *in = *inp;
281 617 : const char *pos = in;
282 :
283 617 : GNUNET_buffer_prealloc (&buf,
284 : left + 40);
285 617 : buf.warn_grow = 0; /* disable, + 40 is just a wild guess */
286 : while (1)
287 17287 : {
288 17904 : int mbl = u8_mblen ((unsigned char *) pos,
289 : left);
290 : unsigned char val;
291 :
292 17904 : if (0 == mbl)
293 617 : break;
294 17287 : val = (unsigned char) *pos;
295 17287 : if ( (1 == mbl) &&
296 : (val <= 0x1F) )
297 : {
298 : /* Should not happen, as input is produced by
299 : * JSON stringification */
300 0 : GNUNET_break (0);
301 0 : lowdump (&buf,
302 : val);
303 : }
304 17287 : else if ( (1 == mbl) && ('\\' == *pos) )
305 : {
306 8 : switch (*(pos + 1))
307 : {
308 1 : case '\\':
309 1 : mbl = 2;
310 1 : GNUNET_buffer_write (&buf,
311 : pos,
312 : mbl);
313 1 : break;
314 3 : case 'u':
315 : {
316 : unsigned int num;
317 : uint32_t n32;
318 : unsigned char res[8];
319 : size_t rlen;
320 :
321 3 : GNUNET_assert ( (1 ==
322 : sscanf (pos + 2,
323 : "%4x",
324 : &num)) ||
325 : (1 ==
326 : sscanf (pos + 2,
327 : "%4X",
328 : &num)) );
329 3 : mbl = 6;
330 3 : n32 = (uint32_t) num;
331 3 : rlen = sizeof (res);
332 3 : u32_to_u8 (&n32,
333 : 1,
334 : res,
335 : &rlen);
336 3 : if ( (1 == rlen) &&
337 3 : (res[0] <= 0x1F) )
338 : {
339 3 : lowdump (&buf,
340 3 : res[0]);
341 : }
342 : else
343 : {
344 0 : GNUNET_buffer_write (&buf,
345 : (const char *) res,
346 : rlen);
347 : }
348 : }
349 3 : break;
350 4 : default:
351 4 : mbl = 2;
352 4 : GNUNET_buffer_write (&buf,
353 : pos,
354 : mbl);
355 4 : break;
356 : }
357 : }
358 : else
359 : {
360 17279 : GNUNET_buffer_write (&buf,
361 : pos,
362 : mbl);
363 : }
364 17287 : left -= mbl;
365 17287 : pos += mbl;
366 : }
367 :
368 : /* 0-terminate buffer */
369 617 : GNUNET_buffer_write (&buf,
370 : "",
371 : 1);
372 617 : GNUNET_free (in);
373 617 : *inp = GNUNET_buffer_reap (&buf,
374 : &olen);
375 617 : return olen;
376 : }
377 :
378 :
379 : /**
380 : * Hash normalized @a j JSON object or array and
381 : * store the result in @a hc.
382 : *
383 : * @param j JSON to hash
384 : * @param[out] hc where to write the hash
385 : */
386 : void
387 436 : TALER_json_hash (const json_t *j,
388 : struct GNUNET_HashCode *hc)
389 : {
390 : char *cstr;
391 : size_t clen;
392 :
393 436 : cstr = json_dumps (j,
394 : JSON_COMPACT | JSON_SORT_KEYS);
395 436 : GNUNET_assert (NULL != cstr);
396 436 : clen = TALER_rfc8785encode (&cstr);
397 436 : GNUNET_CRYPTO_hash (cstr,
398 : clen,
399 : hc);
400 436 : GNUNET_free (cstr);
401 436 : }
402 :
403 :
404 : char **
405 370 : TALER_words_split (const char *command,
406 : const char **extra_args)
407 : {
408 370 : unsigned int i = 0;
409 370 : unsigned int j = 0;
410 370 : unsigned int n = 0;
411 370 : char **res = NULL;
412 :
413 : /* Result is always NULL-terminated */
414 370 : GNUNET_array_append (res, n, NULL);
415 :
416 : /* Split command into words */
417 : while (1)
418 370 : {
419 : char *c;
420 :
421 : /* Skip initial whitespace before word */
422 740 : while (' ' == command[i])
423 0 : i++;
424 :
425 : /* Start of new word */
426 740 : j = i;
427 :
428 : /* Scan to end of word */
429 14558 : while ( (0 != command[j]) && (' ' != command[j]) )
430 13818 : j++;
431 :
432 : /* No new word found */
433 740 : if (i == j)
434 370 : break;
435 :
436 : /* Append word to result */
437 370 : c = GNUNET_malloc (j - i + 1);
438 370 : memcpy (c, &command[i], j - i);
439 370 : c[j - i] = 0;
440 370 : res[n - 1] = c;
441 370 : GNUNET_array_append (res, n, NULL);
442 :
443 : /* Continue at end of word */
444 370 : i = j;
445 : }
446 :
447 : /* Append extra args */
448 370 : if (NULL != extra_args)
449 : {
450 1470 : for (const char **m = extra_args; *m; m++)
451 : {
452 1100 : res[n - 1] = GNUNET_strdup (*m);
453 1100 : GNUNET_array_append (res,
454 : n,
455 : NULL);
456 : }
457 : }
458 :
459 370 : return res;
460 : }
461 :
462 :
463 : void
464 10 : TALER_words_destroy (char **args)
465 : {
466 10 : if (NULL == args)
467 0 : return;
468 40 : for (char **m = args; *m; m++)
469 : {
470 30 : GNUNET_free (*m);
471 30 : *m = NULL;
472 : }
473 10 : GNUNET_free (args);
474 : }
475 :
476 :
477 : bool
478 32 : TALER_is_slug (const char *slug)
479 : {
480 : const unsigned char *p;
481 :
482 32 : if ('\0' == slug[0])
483 1 : return false;
484 :
485 : /* Reject special path components */
486 31 : if (0 == strcmp (slug, ".") ||
487 30 : 0 == strcmp (slug, ".."))
488 2 : return false;
489 :
490 191 : for (p = (const unsigned char *) slug; '\0' != *p; p++)
491 : {
492 171 : unsigned char c = *p;
493 :
494 171 : if (isalnum (c))
495 152 : continue;
496 :
497 19 : switch (c)
498 : {
499 10 : case '-':
500 : case '_':
501 : case '.':
502 : case ':':
503 : case '~':
504 : /* Note: '~' is needed as libeufin-bank allows it in account
505 : usernames, which the core banking API exposes as slugs. */
506 10 : continue;
507 9 : default:
508 9 : return false;
509 : }
510 : }
511 :
512 20 : return true;
513 : }
514 :
515 :
516 : bool
517 17 : TALER_is_session_id (const char *session_id)
518 : {
519 : const unsigned char *p;
520 :
521 : /* The empty session ID is legal, it means "no session". */
522 17 : if ('\0' == session_id[0])
523 1 : return true;
524 :
525 : /* Reject special path components, session IDs are used
526 : as path components of "taler://pay/" URIs. */
527 16 : if ( (0 == strcmp (session_id,
528 15 : ".")) ||
529 15 : (0 == strcmp (session_id,
530 : "..")) )
531 2 : return false;
532 :
533 104 : for (p = (const unsigned char *) session_id; '\0' != *p; p++)
534 : {
535 98 : unsigned char c = *p;
536 :
537 98 : if (isalnum (c))
538 79 : continue;
539 :
540 19 : switch (c)
541 : {
542 11 : case '-':
543 : case '_':
544 : case '.':
545 : case ':':
546 : case '=':
547 : case '~':
548 : /* Note: unlike for slugs, '=' is allowed here as
549 : base64-encoded identifiers (such as the Paivana IDs
550 : used as merchant session IDs) may end in padding. */
551 11 : continue;
552 8 : default:
553 8 : return false;
554 : }
555 : }
556 :
557 6 : return true;
558 : }
559 :
560 :
561 : #ifdef __APPLE__
562 : char *
563 : strchrnul (const char *s,
564 : int c)
565 : {
566 : char *value;
567 : value = strchr (s,
568 : c);
569 : if (NULL == value)
570 : value = &s[strlen (s)];
571 : return value;
572 : }
573 :
574 :
575 : #endif
576 :
577 :
578 : /* end of util.c */
|