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 5451 : 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 5451 : GNUNET_CRYPTO_hash (buf,
53 : buf_size,
54 : &hc);
55 5451 : tmp = GNUNET_STRINGS_data_to_string_alloc (&hc,
56 : sizeof (hc));
57 5451 : GNUNET_memcpy (ret,
58 : tmp,
59 : 8);
60 5451 : GNUNET_free (tmp);
61 5451 : ret[8] = '\0';
62 5451 : return ret;
63 : }
64 :
65 :
66 : void
67 8413 : TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
68 : const struct TALER_DenomFeeSet *fees)
69 : {
70 8413 : TALER_amount_hton (&nbo->withdraw,
71 : &fees->withdraw);
72 8413 : TALER_amount_hton (&nbo->deposit,
73 : &fees->deposit);
74 8413 : TALER_amount_hton (&nbo->refresh,
75 : &fees->refresh);
76 8413 : TALER_amount_hton (&nbo->refund,
77 : &fees->refund);
78 8413 : }
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 290 : TALER_global_fee_set_hton (struct TALER_GlobalFeeSetNBOP *nbo,
98 : const struct TALER_GlobalFeeSet *fees)
99 : {
100 290 : TALER_amount_hton (&nbo->history,
101 : &fees->history);
102 290 : TALER_amount_hton (&nbo->account,
103 : &fees->account);
104 290 : TALER_amount_hton (&nbo->purse,
105 : &fees->purse);
106 290 : }
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 238 : TALER_wire_fee_set_hton (struct TALER_WireFeeSetNBOP *nbo,
124 : const struct TALER_WireFeeSet *fees)
125 : {
126 238 : TALER_amount_hton (&nbo->wire,
127 : &fees->wire);
128 238 : TALER_amount_hton (&nbo->closing,
129 : &fees->closing);
130 238 : }
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 1327 : TALER_denom_fee_check_currency (
186 : const char *currency,
187 : const struct TALER_DenomFeeSet *fees)
188 : {
189 1327 : if (GNUNET_YES !=
190 1327 : TALER_amount_is_currency (&fees->withdraw,
191 : currency))
192 : {
193 0 : GNUNET_break (0);
194 0 : return GNUNET_NO;
195 : }
196 1327 : if (GNUNET_YES !=
197 1327 : TALER_amount_is_currency (&fees->deposit,
198 : currency))
199 : {
200 0 : GNUNET_break (0);
201 0 : return GNUNET_NO;
202 : }
203 1327 : if (GNUNET_YES !=
204 1327 : TALER_amount_is_currency (&fees->refresh,
205 : currency))
206 : {
207 0 : GNUNET_break (0);
208 0 : return GNUNET_NO;
209 : }
210 1327 : if (GNUNET_YES !=
211 1327 : TALER_amount_is_currency (&fees->refund,
212 : currency))
213 : {
214 0 : GNUNET_break (0);
215 0 : return GNUNET_NO;
216 : }
217 1327 : 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 691 : TALER_rfc8785encode (char **inp)
276 : {
277 691 : struct GNUNET_Buffer buf = { 0 };
278 691 : size_t left = strlen (*inp) + 1;
279 : size_t olen;
280 691 : char *in = *inp;
281 691 : const char *pos = in;
282 :
283 691 : GNUNET_buffer_prealloc (&buf,
284 : left + 40);
285 691 : buf.warn_grow = 0; /* disable, + 40 is just a wild guess */
286 : while (1)
287 19103 : {
288 19794 : int mbl = u8_mblen ((unsigned char *) pos,
289 : left);
290 : unsigned char val;
291 :
292 19794 : if (0 == mbl)
293 691 : break;
294 19103 : val = (unsigned char) *pos;
295 19103 : 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 19103 : 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 19095 : GNUNET_buffer_write (&buf,
361 : pos,
362 : mbl);
363 : }
364 19103 : left -= mbl;
365 19103 : pos += mbl;
366 : }
367 :
368 : /* 0-terminate buffer */
369 691 : GNUNET_buffer_write (&buf,
370 : "",
371 : 1);
372 691 : GNUNET_free (in);
373 691 : *inp = GNUNET_buffer_reap (&buf,
374 : &olen);
375 691 : 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 488 : TALER_json_hash (const json_t *j,
388 : struct GNUNET_HashCode *hc)
389 : {
390 : char *cstr;
391 : size_t clen;
392 :
393 488 : cstr = json_dumps (j,
394 : JSON_COMPACT | JSON_SORT_KEYS);
395 488 : GNUNET_assert (NULL != cstr);
396 488 : clen = TALER_rfc8785encode (&cstr);
397 488 : GNUNET_CRYPTO_hash (cstr,
398 : clen,
399 : hc);
400 488 : GNUNET_free (cstr);
401 488 : }
402 :
403 :
404 : char **
405 394 : TALER_words_split (const char *command,
406 : const char **extra_args)
407 : {
408 394 : unsigned int i = 0;
409 394 : unsigned int j = 0;
410 394 : unsigned int n = 0;
411 394 : char **res = NULL;
412 :
413 : /* Result is always NULL-terminated */
414 394 : GNUNET_array_append (res, n, NULL);
415 :
416 : /* Split command into words */
417 : while (1)
418 394 : {
419 : char *c;
420 :
421 : /* Skip initial whitespace before word */
422 788 : while (' ' == command[i])
423 0 : i++;
424 :
425 : /* Start of new word */
426 788 : j = i;
427 :
428 : /* Scan to end of word */
429 15500 : while ( (0 != command[j]) && (' ' != command[j]) )
430 14712 : j++;
431 :
432 : /* No new word found */
433 788 : if (i == j)
434 394 : break;
435 :
436 : /* Append word to result */
437 394 : c = GNUNET_malloc (j - i + 1);
438 394 : memcpy (c, &command[i], j - i);
439 394 : c[j - i] = 0;
440 394 : res[n - 1] = c;
441 394 : GNUNET_array_append (res, n, NULL);
442 :
443 : /* Continue at end of word */
444 394 : i = j;
445 : }
446 :
447 : /* Append extra args */
448 394 : if (NULL != extra_args)
449 : {
450 1566 : for (const char **m = extra_args; *m; m++)
451 : {
452 1172 : res[n - 1] = GNUNET_strdup (*m);
453 1172 : GNUNET_array_append (res,
454 : n,
455 : NULL);
456 : }
457 : }
458 :
459 394 : 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 : /**
478 : * Is @a c an ASCII letter or digit?
479 : *
480 : * Deliberately not isalnum(): that function is locale-dependent, and
481 : * GNUNET_PROGRAM_run() calls setlocale(LC_ALL, "") when NLS is enabled.
482 : * Under a single-byte non-ASCII locale (ISO-8859-x) isalnum() accepts
483 : * bytes >= 0x80, which would make the slug and session ID character sets
484 : * depend on the environment the service happens to run in.
485 : *
486 : * @param c character to test
487 : * @return true if @a c is in [A-Za-z0-9]
488 : */
489 : static bool
490 275 : is_ascii_alnum (unsigned char c)
491 : {
492 275 : return ( ( ('a' <= c) && ('z' >= c) ) ||
493 617 : ( ('A' <= c) && ('Z' >= c) ) ||
494 67 : ( ('0' <= c) && ('9' >= c) ) );
495 : }
496 :
497 :
498 : bool
499 34 : TALER_is_slug (const char *slug)
500 : {
501 : const unsigned char *p;
502 :
503 34 : if ('\0' == slug[0])
504 1 : return false;
505 :
506 : /* Reject special path components */
507 33 : if (0 == strcmp (slug, ".") ||
508 32 : 0 == strcmp (slug, ".."))
509 2 : return false;
510 :
511 194 : for (p = (const unsigned char *) slug; '\0' != *p; p++)
512 : {
513 174 : unsigned char c = *p;
514 :
515 174 : if (is_ascii_alnum (c))
516 153 : continue;
517 :
518 21 : switch (c)
519 : {
520 10 : case '-':
521 : case '_':
522 : case '.':
523 : case ':':
524 : case '~':
525 : /* Note: '~' is needed as libeufin-bank allows it in account
526 : usernames, which the core banking API exposes as slugs. */
527 10 : continue;
528 11 : default:
529 11 : return false;
530 : }
531 : }
532 :
533 20 : return true;
534 : }
535 :
536 :
537 : bool
538 19 : TALER_is_session_id (const char *session_id)
539 : {
540 : const unsigned char *p;
541 :
542 : /* The empty session ID is legal, it means "no session". */
543 19 : if ('\0' == session_id[0])
544 1 : return true;
545 :
546 : /* Reject special path components, session IDs are used
547 : as path components of "taler://pay/" URIs. */
548 18 : if ( (0 == strcmp (session_id,
549 17 : ".")) ||
550 17 : (0 == strcmp (session_id,
551 : "..")) )
552 2 : return false;
553 :
554 107 : for (p = (const unsigned char *) session_id; '\0' != *p; p++)
555 : {
556 101 : unsigned char c = *p;
557 :
558 101 : if (is_ascii_alnum (c))
559 80 : continue;
560 :
561 21 : switch (c)
562 : {
563 11 : case '-':
564 : case '_':
565 : case '.':
566 : case ':':
567 : case '=':
568 : case '~':
569 : /* Note: unlike for slugs, '=' is allowed here as
570 : base64-encoded identifiers (such as the Paivana IDs
571 : used as merchant session IDs) may end in padding. */
572 11 : continue;
573 10 : default:
574 10 : return false;
575 : }
576 : }
577 :
578 6 : return true;
579 : }
580 :
581 :
582 : #ifdef __APPLE__
583 : char *
584 : strchrnul (const char *s,
585 : int c)
586 : {
587 : char *value;
588 : value = strchr (s,
589 : c);
590 : if (NULL == value)
591 : value = &s[strlen (s)];
592 : return value;
593 : }
594 :
595 :
596 : #endif
597 :
598 :
599 : /* end of util.c */
|