exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

payto.c (20872B)


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