exchange

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

payto.c (20391B)


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