payto.c (21730B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2019-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 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 payto.c 18 * @brief Common utility functions for dealing with payto://-URIs 19 * @author Florian Dold 20 */ 21 #include "platform.h" /* UNNECESSARY? */ 22 #include "taler/taler_util.h" 23 24 25 /** 26 * Prefix of PAYTO URLs. 27 */ 28 #define PAYTO "payto://" 29 30 31 int 32 TALER_full_payto_cmp (const struct TALER_FullPayto a, 33 const struct TALER_FullPayto b) 34 { 35 if ( (NULL == a.full_payto) && 36 (NULL == b.full_payto) ) 37 return 0; 38 if (NULL == a.full_payto) 39 return -1; 40 if (NULL == b.full_payto) 41 return 1; 42 return strcmp (a.full_payto, 43 b.full_payto); 44 } 45 46 47 bool 48 TALER_payto_is_wallet (const char *payto_uri) 49 { 50 return 51 (0 == strncasecmp (payto_uri, 52 "payto://taler-reserve/", 53 strlen ("payto://taler-reserve/"))) || 54 (0 == strncasecmp (payto_uri, 55 "payto://taler-reserve-http/", 56 strlen ("payto://taler-reserve-http/"))); 57 } 58 59 60 int 61 TALER_normalized_payto_cmp (const struct TALER_NormalizedPayto a, 62 const struct TALER_NormalizedPayto b) 63 { 64 if ( (NULL == a.normalized_payto) && 65 (NULL == b.normalized_payto) ) 66 return 0; 67 if (NULL == a.normalized_payto) 68 return -1; 69 if (NULL == b.normalized_payto) 70 return 1; 71 return strcmp (a.normalized_payto, 72 b.normalized_payto); 73 } 74 75 76 void 77 TALER_full_payto_normalize_and_hash (const struct TALER_FullPayto in, 78 struct TALER_NormalizedPaytoHashP *out) 79 { 80 struct TALER_NormalizedPayto normalized_payto_uri; 81 82 normalized_payto_uri 83 = TALER_payto_normalize (in); 84 if (NULL == normalized_payto_uri.normalized_payto) 85 { 86 /* Callers must have validated @a in; never crash on a URI that 87 slipped through. */ 88 GNUNET_break (0); 89 memset (out, 90 0, 91 sizeof (*out)); 92 return; 93 } 94 TALER_normalized_payto_hash (normalized_payto_uri, 95 out); 96 GNUNET_free (normalized_payto_uri.normalized_payto); 97 } 98 99 100 /** 101 * Compare two full payto URIs for equality in their normalized form. 102 * 103 * @param a a full payto URI, NULL is permitted 104 * @param b a full payto URI, NULL is permitted 105 * @return 0 if both are equal, otherwise -1 or 1 106 */ 107 int 108 TALER_full_payto_normalize_and_cmp (const struct TALER_FullPayto a, 109 const struct TALER_FullPayto b) 110 { 111 struct TALER_NormalizedPayto an 112 = TALER_payto_normalize (a); 113 struct TALER_NormalizedPayto bn 114 = TALER_payto_normalize (b); 115 int ret; 116 117 ret = TALER_normalized_payto_cmp (an, 118 bn); 119 GNUNET_free (an.normalized_payto); 120 GNUNET_free (bn.normalized_payto); 121 return ret; 122 } 123 124 125 /** 126 * Extract the value under @a key from the URI parameters. 127 * 128 * @param fpayto_uri the full payto URL to parse 129 * @param search_key key to look for, including "=" 130 * @return NULL if the @a key parameter is not found. 131 * The caller should free the returned value. 132 */ 133 static char * 134 payto_get_key (const struct TALER_FullPayto fpayto_uri, 135 const char *search_key) 136 { 137 const char *payto_uri = fpayto_uri.full_payto; 138 const char *key; 139 const char *value_start; 140 const char *value_end; 141 142 key = strchr (payto_uri, 143 (unsigned char) '?'); 144 if (NULL == key) 145 return NULL; 146 147 do { 148 if (0 == strncasecmp (++key, 149 search_key, 150 strlen (search_key))) 151 { 152 value_start = strchr (key, 153 (unsigned char) '='); 154 if (NULL == value_start) 155 return NULL; 156 value_end = strchrnul (value_start, 157 (unsigned char) '&'); 158 159 return GNUNET_strndup (value_start + 1, 160 value_end - value_start - 1); 161 } 162 } while ( (key = strchr (key, 163 (unsigned char) '&')) ); 164 return NULL; 165 } 166 167 168 char * 169 TALER_payto_get_subject (const struct TALER_FullPayto payto_uri) 170 { 171 return payto_get_key (payto_uri, 172 "subject="); 173 } 174 175 176 char * 177 TALER_payto_get_method (const char *payto_uri) 178 { 179 const char *start; 180 const char *end; 181 182 if (0 != strncasecmp (payto_uri, 183 PAYTO, 184 strlen (PAYTO))) 185 return NULL; 186 start = &payto_uri[strlen (PAYTO)]; 187 end = strchr (start, 188 (unsigned char) '/'); 189 if (NULL == end) 190 return NULL; 191 return GNUNET_strndup (start, 192 end - start); 193 } 194 195 196 char * 197 TALER_xtalerbank_account_from_payto (const struct TALER_FullPayto payto) 198 { 199 const char *host; 200 const char *beg; 201 const char *nxt; 202 const char *end; 203 204 if (0 != strncasecmp (payto.full_payto, 205 PAYTO "x-taler-bank/", 206 strlen (PAYTO "x-taler-bank/"))) 207 { 208 GNUNET_break_op (0); 209 return NULL; 210 } 211 host = &payto.full_payto[strlen (PAYTO "x-taler-bank/")]; 212 beg = strchr (host, 213 '/'); 214 if (NULL == beg) 215 { 216 GNUNET_break_op (0); 217 return NULL; 218 } 219 beg++; /* now points to $ACCOUNT or $PATH */ 220 nxt = strchr (beg, 221 '/'); 222 end = strchr (beg, 223 '?'); 224 if (NULL == end) 225 end = &beg[strlen (beg)]; 226 while ( (NULL != nxt) && 227 (end - nxt > 0) ) 228 { 229 beg = nxt + 1; 230 nxt = strchr (beg, 231 '/'); 232 } 233 return GNUNET_strndup (beg, 234 end - beg); 235 } 236 237 238 /** 239 * Validate payto://iban/ account URL (only account information, 240 * wire subject and amount are ignored). 241 * 242 * @param payto_uri payto URL to parse 243 * @return NULL on success, otherwise an error message 244 * to be freed by the caller 245 */ 246 static char * 247 validate_payto_iban (const char *payto_uri) 248 { 249 size_t slen = strlen (payto_uri); 250 const char *iban; 251 const char *q; 252 char *result; 253 char *err; 254 255 #define IBAN_PREFIX "payto://iban/" 256 if (0 != strncasecmp (payto_uri, 257 IBAN_PREFIX, 258 strlen (IBAN_PREFIX))) 259 return NULL; /* not an IBAN */ 260 q = memchr (payto_uri, 261 '?', 262 slen); 263 /* RFC 8905, Section 7.3: the path is either "$IBAN" or "$BIC/$IBAN". 264 Reject anything else here, so that normalize_payto_iban() -- which 265 enforces the same rule by returning NULL -- can never be handed a 266 URI that passed validation. */ 267 { 268 unsigned int sc = 0; 269 size_t plen = (NULL == q) 270 ? slen 271 : (size_t) (q - payto_uri); 272 273 for (size_t i = 0; i < plen; i++) 274 if ('/' == payto_uri[i]) 275 sc++; 276 if ( (sc < 3) || 277 (sc > 4) ) 278 return GNUNET_strdup ( 279 "'iban' payto:// URI must have one or two path components"); 280 } 281 /* Find the last '/' that is part of the path, i.e. before any '?' 282 query part ('/' is a legal character in a query value and must 283 not be mistaken for the path separator). */ 284 { 285 const char *slash; 286 287 slash = memrchr (payto_uri, 288 '/', 289 (NULL == q) 290 ? slen 291 : q - payto_uri); 292 GNUNET_assert (NULL != slash); /* prefix guarantees a '/' */ 293 iban = slash + 1; 294 } 295 #undef IBAN_PREFIX 296 if (NULL != q) 297 { 298 result = GNUNET_strndup (iban, 299 q - iban); 300 } 301 else 302 { 303 result = GNUNET_strdup (iban); 304 } 305 if (NULL != 306 (err = TALER_iban_validate (result))) 307 { 308 GNUNET_free (result); 309 return err; 310 } 311 GNUNET_free (result); 312 return NULL; 313 } 314 315 316 /** 317 * Validate payto://x-taler-bank/ account URL (only account information, 318 * wire subject and amount are ignored). 319 * 320 * @param payto_uri payto URL to parse 321 * @return NULL on success, otherwise an error message 322 * to be freed by the caller 323 */ 324 static char * 325 validate_payto_xtalerbank (const char *payto_uri) 326 { 327 const char *user; 328 const char *nxt; 329 const char *beg; 330 const char *end; 331 const char *host; 332 bool dot_ok; 333 bool post_colon; 334 bool port_ok; 335 336 #define XTALERBANK_PREFIX PAYTO "x-taler-bank/" 337 if (0 != strncasecmp (payto_uri, 338 XTALERBANK_PREFIX, 339 strlen (XTALERBANK_PREFIX))) 340 return NULL; /* not an x-taler-bank URI */ 341 host = &payto_uri[strlen (XTALERBANK_PREFIX)]; 342 #undef XTALERBANK_PREFIX 343 beg = strchr (host, 344 '/'); 345 if (NULL == beg) 346 { 347 return GNUNET_strdup ("account name missing"); 348 } 349 beg++; /* now points to $ACCOUNT or $PATH */ 350 nxt = strchr (beg, 351 '/'); 352 end = strchr (beg, 353 '?'); 354 if (NULL == end) 355 { 356 return GNUNET_strdup ("'receiver-name' parameter missing"); 357 } 358 while ( (NULL != nxt) && 359 (end - nxt > 0) ) 360 { 361 beg = nxt + 1; 362 nxt = strchr (beg, 363 '/'); 364 } 365 user = beg; 366 if (user == host + 1) 367 { 368 return GNUNET_strdup ("domain name missing"); 369 } 370 if ('-' == host[0]) 371 return GNUNET_strdup ("invalid character '-' at start of domain name"); 372 dot_ok = false; 373 post_colon = false; 374 port_ok = false; 375 while (host != user) 376 { 377 char c = host[0]; 378 379 if ('/' == c) 380 { 381 /* path started, do not care about characters 382 in the path */ 383 break; 384 } 385 if (':' == c) 386 { 387 post_colon = true; 388 host++; 389 continue; 390 } 391 if (post_colon) 392 { 393 if (! ( ('0' <= c) && ('9' >= c) ) ) 394 { 395 char *err; 396 397 GNUNET_asprintf (&err, 398 "invalid character '%c' in port", 399 c); 400 return err; 401 } 402 port_ok = true; 403 } 404 else 405 { 406 if ('.' == c) 407 { 408 if (! dot_ok) 409 return GNUNET_strdup ("invalid domain name (misplaced '.')"); 410 dot_ok = false; 411 } 412 else 413 { 414 if (! ( ('-' == c) || 415 ('_' == c) || 416 ( ('0' <= c) && ('9' >= c) ) || 417 ( ('a' <= c) && ('z' >= c) ) || 418 ( ('A' <= c) && ('Z' >= c) ) ) ) 419 { 420 char *err; 421 422 GNUNET_asprintf (&err, 423 "invalid character '%c' in domain name", 424 c); 425 return err; 426 } 427 dot_ok = true; 428 } 429 } 430 host++; 431 } 432 if (post_colon && (! port_ok) ) 433 { 434 return GNUNET_strdup ("port missing after ':'"); 435 } 436 return NULL; 437 } 438 439 440 /** 441 * Generic validation of a payto:// URI. Checks the prefix 442 * and character set. 443 * 444 * @param payto_uri URI to validate 445 * @return NULL on success, otherwise an error message 446 */ 447 static char * 448 payto_validate (const char *payto_uri) 449 { 450 char *ret; 451 const char *start; 452 const char *end; 453 454 if (0 != strncasecmp (payto_uri, 455 PAYTO, 456 strlen (PAYTO))) 457 return GNUNET_strdup ("invalid prefix"); 458 for (unsigned int i = 0; '\0' != payto_uri[i]; i++) 459 { 460 /* This is more strict than RFC 8905, alas we do not need to support messages/instructions/etc., 461 and it is generally better to start with a narrow whitelist; we can be more permissive later ...*/ 462 #define ALLOWED_CHARACTERS \ 463 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:$&?!-_.,;=*+%~@()[]" 464 if (NULL == strchr (ALLOWED_CHARACTERS, 465 (int) payto_uri[i])) 466 { 467 GNUNET_asprintf (&ret, 468 "Encountered invalid character `%c' at offset %u in payto URI `%s'", 469 payto_uri[i], 470 i, 471 payto_uri); 472 return ret; 473 } 474 #undef ALLOWED_CHARACTERS 475 } 476 477 start = &payto_uri[strlen (PAYTO)]; 478 end = strchr (start, 479 (unsigned char) '/'); 480 if (NULL == end) 481 return GNUNET_strdup ("missing '/' in payload"); 482 483 if (NULL != (ret = validate_payto_iban (payto_uri))) 484 return ret; /* got a definitive answer */ 485 if (NULL != (ret = validate_payto_xtalerbank (payto_uri))) 486 return ret; /* got a definitive answer */ 487 488 /* Insert validation calls for other bank account validation methods here! */ 489 490 return NULL; 491 } 492 493 494 char * 495 TALER_normalized_payto_validate (const struct TALER_NormalizedPayto npayto_uri) 496 { 497 const char *payto_uri = npayto_uri.normalized_payto; 498 char *ret; 499 500 ret = payto_validate (payto_uri); 501 if (NULL != ret) 502 return ret; 503 return NULL; 504 } 505 506 507 char * 508 TALER_payto_validate (const struct TALER_FullPayto fpayto_uri) 509 { 510 const char *payto_uri = fpayto_uri.full_payto; 511 char *ret; 512 513 ret = payto_validate (payto_uri); 514 if (NULL != ret) 515 return ret; 516 { 517 char *target; 518 519 target = payto_get_key (fpayto_uri, 520 "receiver-name="); 521 if (NULL == target) 522 return GNUNET_strdup ("'receiver-name' parameter missing"); 523 GNUNET_free (target); 524 } 525 526 return NULL; 527 } 528 529 530 char * 531 TALER_payto_get_receiver_name (const struct TALER_FullPayto fpayto) 532 { 533 char *err; 534 535 err = TALER_payto_validate (fpayto); 536 if (NULL != err) 537 { 538 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 539 "Invalid payto://-URI `%s': %s\n", 540 fpayto.full_payto, 541 err); 542 GNUNET_free (err); 543 return NULL; 544 } 545 return payto_get_key (fpayto, 546 "receiver-name="); 547 } 548 549 550 /** 551 * Normalize "payto://x-taler-bank/$HOSTNAME/[$PATH/]$USERNAME" 552 * URI in @a input. 553 * 554 * Converts to lower-case, except for [$PATH/]$USERNAME which 555 * is case-sensitive. 556 * 557 * @param len number of bytes in @a input 558 * @param input input URL 559 * @return NULL on error, otherwise 0-terminated canonicalized URI. 560 */ 561 static char * 562 normalize_payto_x_taler_bank (size_t len, 563 const char input[static len]) 564 { 565 char *res = GNUNET_malloc (len + 1); 566 unsigned int sc = 0; 567 568 for (unsigned int i = 0; i<len; i++) 569 { 570 char c = input[i]; 571 572 if ('/' == c) 573 sc++; 574 if (sc < 4) 575 res[i] = (char) tolower ((int) c); 576 else 577 res[i] = c; 578 } 579 return res; 580 } 581 582 583 /** 584 * Normalize "payto://iban[/$BIC]/$IBAN" 585 * URI in @a input. 586 * 587 * Removes $BIC (if present) and converts $IBAN to upper-case and prefix to 588 * lower-case. 589 * 590 * @param len number of bytes in @a input 591 * @param input input URL 592 * @return NULL on error, otherwise 0-terminated canonicalized URI. 593 */ 594 static char * 595 normalize_payto_iban (size_t len, 596 const char input[static len]) 597 { 598 char *res; 599 size_t pos = 0; 600 unsigned int sc = 0; 601 bool have_bic; 602 603 for (unsigned int i = 0; i<len; i++) 604 if ('/' == input[i]) 605 sc++; 606 if ( (sc > 4) || 607 (sc < 3) ) 608 { 609 GNUNET_break (0); 610 return NULL; 611 } 612 have_bic = (4 == sc); 613 res = GNUNET_malloc (len + 1); 614 sc = 0; 615 for (unsigned int i = 0; i<len; i++) 616 { 617 char c = input[i]; 618 619 if ('/' == c) 620 sc++; 621 switch (sc) 622 { 623 case 0: /* payto: */ 624 case 1: /* / */ 625 case 2: /* /iban */ 626 res[pos++] = (char) tolower ((int) c); 627 break; 628 case 3: /* /$BIC or /$IBAN */ 629 if (have_bic) 630 continue; 631 res[pos++] = (char) toupper ((int) c); 632 break; 633 case 4: /* /$IBAN */ 634 res[pos++] = (char) toupper ((int) c); 635 break; 636 } 637 } 638 GNUNET_assert (pos <= len); 639 return res; 640 } 641 642 643 /** 644 * Normalize "payto://upi/$EMAIL" 645 * URI in @a input. 646 * 647 * Converts to lower-case. 648 * 649 * @param len number of bytes in @a input 650 * @param input input URL 651 * @return NULL on error, otherwise 0-terminated canonicalized URI. 652 */ 653 static char * 654 normalize_payto_upi (size_t len, 655 const char input[static len]) 656 { 657 char *res = GNUNET_malloc (len + 1); 658 659 for (unsigned int i = 0; i<len; i++) 660 { 661 char c = input[i]; 662 663 res[i] = (char) tolower ((int) c); 664 } 665 return res; 666 } 667 668 669 /** 670 * Normalize "payto://bitcoin/$ADDRESS" 671 * URI in @a input. 672 * 673 * Converts to lower-case, except for $ADDRESS which 674 * is case-sensitive. 675 * 676 * @param len number of bytes in @a input 677 * @param input input URL 678 * @return NULL on error, otherwise 0-terminated canonicalized URI. 679 */ 680 static char * 681 normalize_payto_bitcoin (size_t len, 682 const char input[static len]) 683 { 684 char *res = GNUNET_malloc (len + 1); 685 unsigned int sc = 0; 686 687 for (unsigned int i = 0; i<len; i++) 688 { 689 char c = input[i]; 690 691 if ('/' == c) 692 sc++; 693 if (sc < 3) 694 res[i] = (char) tolower ((int) c); 695 else 696 res[i] = c; 697 } 698 return res; 699 } 700 701 702 /** 703 * Normalize "payto://ilp/$NAME" 704 * URI in @a input. 705 * 706 * Converts to lower-case. 707 * 708 * @param len number of bytes in @a input 709 * @param input input URL 710 * @return NULL on error, otherwise 0-terminated canonicalized URI. 711 */ 712 static char * 713 normalize_payto_ilp (size_t len, 714 const char input[static len]) 715 { 716 char *res = GNUNET_malloc (len + 1); 717 718 for (unsigned int i = 0; i<len; i++) 719 { 720 char c = input[i]; 721 722 res[i] = (char) tolower ((int) c); 723 } 724 return res; 725 } 726 727 728 struct TALER_NormalizedPayto 729 TALER_payto_normalize (const struct TALER_FullPayto input) 730 { 731 struct TALER_NormalizedPayto npto = { 732 .normalized_payto = NULL 733 }; 734 char *method; 735 const char *end; 736 char *ret; 737 738 { 739 char *err; 740 741 err = TALER_payto_validate (input); 742 if (NULL != err) 743 { 744 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 745 "Malformed payto://-URI `%s': %s\n", 746 input.full_payto, 747 err); 748 GNUNET_free (err); 749 return npto; 750 } 751 } 752 method = TALER_payto_get_method (input.full_payto); 753 if (NULL == method) 754 { 755 GNUNET_break (0); 756 return npto; 757 } 758 end = strchr (input.full_payto, 759 '?'); 760 if (NULL == end) 761 end = &input.full_payto[strlen (input.full_payto)]; 762 if (0 == strcasecmp (method, 763 "x-taler-bank")) 764 ret = normalize_payto_x_taler_bank (end - input.full_payto, 765 input.full_payto); 766 else if (0 == strcasecmp (method, 767 "iban")) 768 ret = normalize_payto_iban (end - input.full_payto, 769 input.full_payto); 770 else if (0 == strcasecmp (method, 771 "upi")) 772 ret = normalize_payto_upi (end - input.full_payto, 773 input.full_payto); 774 else if (0 == strcasecmp (method, 775 "bitcoin")) 776 ret = normalize_payto_bitcoin (end - input.full_payto, 777 input.full_payto); 778 else if (0 == strcasecmp (method, 779 "ilp")) 780 ret = normalize_payto_ilp (end - input.full_payto, 781 input.full_payto); 782 else 783 ret = GNUNET_strndup (input.full_payto, 784 end - input.full_payto); 785 GNUNET_free (method); 786 npto.normalized_payto = ret; 787 return npto; 788 } 789 790 791 void 792 TALER_normalized_payto_hash (const struct TALER_NormalizedPayto npayto, 793 struct TALER_NormalizedPaytoHashP *h_npayto) 794 { 795 struct GNUNET_HashCode sha512; 796 797 GNUNET_static_assert (sizeof (sha512) > sizeof (*h_npayto)); 798 GNUNET_CRYPTO_hash (npayto.normalized_payto, 799 strlen (npayto.normalized_payto) + 1, 800 &sha512); 801 /* truncate */ 802 GNUNET_memcpy (h_npayto, 803 &sha512, 804 sizeof (*h_npayto)); 805 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 806 "Normalized hash of normalized payto `%s' is %16s\n", 807 npayto.normalized_payto, 808 GNUNET_h2s_full (&sha512)); 809 } 810 811 812 void 813 TALER_full_payto_hash (const struct TALER_FullPayto fpayto, 814 struct TALER_FullPaytoHashP *h_fpayto) 815 { 816 struct GNUNET_HashCode sha512; 817 818 GNUNET_static_assert (sizeof (sha512) > sizeof (*h_fpayto)); 819 GNUNET_CRYPTO_hash (fpayto.full_payto, 820 strlen (fpayto.full_payto) + 1, 821 &sha512); 822 /* truncate */ 823 GNUNET_memcpy (h_fpayto, 824 &sha512, 825 sizeof (*h_fpayto)); 826 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 827 "Full hash of full payto `%s' is %16s\n", 828 fpayto.full_payto, 829 GNUNET_h2s_full (&sha512)); 830 } 831 832 833 struct TALER_NormalizedPayto 834 TALER_reserve_make_payto (const char *exchange_url, 835 const struct TALER_ReservePublicKeyP *reserve_pub) 836 { 837 struct TALER_NormalizedPayto npto = { 838 .normalized_payto = NULL 839 }; 840 char pub_str[sizeof (*reserve_pub) * 2]; 841 char *end; 842 bool is_http; 843 char *reserve_url; 844 845 end = GNUNET_STRINGS_data_to_string ( 846 reserve_pub, 847 sizeof (*reserve_pub), 848 pub_str, 849 sizeof (pub_str)); 850 *end = '\0'; 851 if (0 == strncmp (exchange_url, 852 "http://", 853 strlen ("http://"))) 854 { 855 is_http = true; 856 exchange_url = &exchange_url[strlen ("http://")]; 857 } 858 else if (0 == strncmp (exchange_url, 859 "https://", 860 strlen ("https://"))) 861 { 862 is_http = false; 863 exchange_url = &exchange_url[strlen ("https://")]; 864 } 865 else 866 { 867 GNUNET_break (0); 868 return npto; 869 } 870 /* exchange_url includes trailing '/' */ 871 GNUNET_asprintf (&reserve_url, 872 "payto://%s/%s%s", 873 is_http ? "taler-reserve-http" : "taler-reserve", 874 exchange_url, 875 pub_str); 876 npto.normalized_payto = reserve_url; 877 return npto; 878 } 879 880 881 /* end of payto.c */