diff options
Diffstat (limited to 'src/util/payto.c')
-rw-r--r-- | src/util/payto.c | 509 |
1 files changed, 474 insertions, 35 deletions
diff --git a/src/util/payto.c b/src/util/payto.c index 58f1bf635..6092b73fd 100644 --- a/src/util/payto.c +++ b/src/util/payto.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2019-2021 Taler Systems SA + Copyright (C) 2019-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -70,13 +70,6 @@ payto_get_key (const char *payto_uri, } -/** - * Extract the subject value from the URI parameters. - * - * @param payto_uri the URL to parse - * @return NULL if the subject parameter is not found. - * The caller should free the returned value. - */ char * TALER_payto_get_subject (const char *payto_uri) { @@ -85,14 +78,6 @@ TALER_payto_get_subject (const char *payto_uri) } -/** - * Obtain the payment method from a @a payto_uri. The - * format of a payto URI is 'payto://$METHOD/$SOMETHING'. - * We return $METHOD. - * - * @param payto_uri the URL to parse - * @return NULL on error (malformed @a payto_uri) - */ char * TALER_payto_get_method (const char *payto_uri) { @@ -113,35 +98,43 @@ TALER_payto_get_method (const char *payto_uri) } -/** - * Obtain the account name from a payto URL. The format - * of the @a payto URL is 'payto://x-taler-bank/$HOSTNAME/$ACCOUNT[?PARAMS]'. - * We check the first part matches, skip over the $HOSTNAME - * and return the $ACCOUNT portion. - * - * @param payto an x-taler-bank payto URL - * @return only the account name from the @a payto URL, NULL if not an x-taler-bank - * payto URL - */ char * TALER_xtalerbank_account_from_payto (const char *payto) { + const char *host; const char *beg; + const char *nxt; const char *end; if (0 != strncasecmp (payto, PAYTO "x-taler-bank/", strlen (PAYTO "x-taler-bank/"))) + { + GNUNET_break_op (0); return NULL; - beg = strchr (&payto[strlen (PAYTO "x-taler-bank/")], + } + host = &payto[strlen (PAYTO "x-taler-bank/")]; + beg = strchr (host, '/'); if (NULL == beg) + { + GNUNET_break_op (0); return NULL; - beg++; /* now points to $ACCOUNT */ + } + beg++; /* now points to $ACCOUNT or $PATH */ + nxt = strchr (beg, + '/'); end = strchr (beg, '?'); if (NULL == end) - return GNUNET_strdup (beg); /* optional part is missing */ + end = &beg[strlen (beg)]; + while ( (NULL != nxt) && + (end - nxt > 0) ) + { + beg = nxt + 1; + nxt = strchr (beg, + '/'); + } return GNUNET_strndup (beg, end - beg); } @@ -168,7 +161,6 @@ validate_payto_iban (const char *account_url) IBAN_PREFIX, strlen (IBAN_PREFIX))) return NULL; /* not an IBAN */ - iban = strrchr (account_url, '/') + 1; #undef IBAN_PREFIX q = strchr (iban, @@ -203,12 +195,137 @@ validate_payto_iban (const char *account_url) /** - * Check that a payto:// URI is well-formed. + * Validate payto://x-taler-bank/ account URL (only account information, + * wire subject and amount are ignored). * - * @param payto_uri the URL to check - * @return NULL on success, otherwise an error - * message to be freed by the caller! + * @param account_url payto URL to parse + * @return NULL on success, otherwise an error message + * to be freed by the caller */ +static char * +validate_payto_xtalerbank (const char *account_url) +{ + const char *user; + const char *nxt; + const char *beg; + const char *end; + const char *host; + bool dot_ok; + bool post_colon; + bool port_ok; + +#define XTALERBANK_PREFIX PAYTO "x-taler-bank/" + if (0 != strncasecmp (account_url, + XTALERBANK_PREFIX, + strlen (XTALERBANK_PREFIX))) + return NULL; /* not an IBAN */ + host = &account_url[strlen (XTALERBANK_PREFIX)]; +#undef XTALERBANK_PREFIX + beg = strchr (host, + '/'); + if (NULL == beg) + { + return GNUNET_strdup ("account name missing"); + } + beg++; /* now points to $ACCOUNT or $PATH */ + nxt = strchr (beg, + '/'); + end = strchr (beg, + '?'); + if (NULL == end) + { + return GNUNET_strdup ("'receiver-name' parameter missing"); + } + while ( (NULL != nxt) && + (end - nxt > 0) ) + { + beg = nxt + 1; + nxt = strchr (beg, + '/'); + } + user = beg; + if (user == host + 1) + { + return GNUNET_strdup ("domain name missing"); + } + if ('-' == host[0]) + return GNUNET_strdup ("invalid character '-' at start of domain name"); + dot_ok = false; + post_colon = false; + port_ok = false; + while (host != user) + { + char c = host[0]; + + if ('/' == c) + { + /* path started, do not care about characters + in the path */ + break; + } + if (':' == c) + { + post_colon = true; + host++; + continue; + } + if (post_colon) + { + if (! ( ('0' <= c) && ('9' >= c) ) ) + { + char *err; + + GNUNET_asprintf (&err, + "invalid character '%c' in port", + c); + return err; + } + port_ok = true; + } + else + { + if ('.' == c) + { + if (! dot_ok) + return GNUNET_strdup ("invalid domain name (misplaced '.')"); + dot_ok = false; + } + else + { + if (! ( ('-' == c) || + ( ('0' <= c) && ('9' >= c) ) || + ( ('a' <= c) && ('z' >= c) ) || + ( ('A' <= c) && ('Z' >= c) ) ) ) + { + char *err; + + GNUNET_asprintf (&err, + "invalid character '%c' in domain name", + c); + return err; + } + dot_ok = true; + } + } + host++; + } + if (post_colon && (! port_ok) ) + { + return GNUNET_strdup ("port missing after ':'"); + } + { + char *target; + + target = payto_get_key (account_url, + "receiver-name="); + if (NULL == target) + return GNUNET_strdup ("'receiver-name' parameter missing"); + GNUNET_free (target); + } + return NULL; +} + + char * TALER_payto_validate (const char *payto_uri) { @@ -225,7 +342,7 @@ TALER_payto_validate (const char *payto_uri) /* This is more strict than RFC 8905, alas we do not need to support messages/instructions/etc., and it is generally better to start with a narrow whitelist; we can be more permissive later ...*/ #define ALLOWED_CHARACTERS \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,=" + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,=+%~" if (NULL == strchr (ALLOWED_CHARACTERS, (int) payto_uri[i])) { @@ -249,8 +366,330 @@ TALER_payto_validate (const char *payto_uri) if (NULL != (ret = validate_payto_iban (payto_uri))) return ret; /* got a definitive answer */ + if (NULL != (ret = validate_payto_xtalerbank (payto_uri))) + return ret; /* got a definitive answer */ /* Insert other bank account validation methods here later! */ return NULL; } + + +char * +TALER_payto_get_receiver_name (const char *payto) +{ + char *err; + + err = TALER_payto_validate (payto); + if (NULL != err) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid payto://-URI `%s': %s\n", + payto, + err); + GNUNET_free (err); + return NULL; + } + return payto_get_key (payto, + "receiver-name="); +} + + +/** + * Normalize "payto://x-taler-bank/$HOSTNAME/[$PATH/]$USERNAME" + * URI in @a input. + * + * Converts to lower-case, except for [$PATH/]$USERNAME which + * is case-sensitive. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_x_taler_bank (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + unsigned int sc = 0; + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + if ('/' == c) + sc++; + if (sc < 4) + res[i] = (char) tolower ((int) c); + else + res[i] = c; + } + return res; +} + + +/** + * Normalize "payto://iban[/$BIC]/$IBAN" + * URI in @a input. + * + * Removes $BIC (if present) and converts $IBAN to upper-case and prefix to + * lower-case. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_iban (size_t len, + const char input[static len]) +{ + char *res; + size_t pos = 0; + unsigned int sc = 0; + bool have_bic; + + for (unsigned int i = 0; i<len; i++) + if ('/' == input[i]) + sc++; + if ( (sc > 4) || + (sc < 3) ) + { + GNUNET_break (0); + return NULL; + } + have_bic = (4 == sc); + res = GNUNET_malloc (len + 1); + sc = 0; + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + if ('/' == c) + sc++; + switch (sc) + { + case 0: /* payto: */ + case 1: /* / */ + case 2: /* /iban */ + res[pos++] = (char) tolower ((int) c); + break; + case 3: /* /$BIC or /$IBAN */ + if (have_bic) + continue; + res[pos++] = (char) toupper ((int) c); + break; + case 4: /* /$IBAN */ + res[pos++] = (char) toupper ((int) c); + break; + } + } + GNUNET_assert (pos <= len); + return res; +} + + +/** + * Normalize "payto://upi/$EMAIL" + * URI in @a input. + * + * Converts to lower-case. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_upi (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + res[i] = (char) tolower ((int) c); + } + return res; +} + + +/** + * Normalize "payto://bitcoin/$ADDRESS" + * URI in @a input. + * + * Converts to lower-case, except for $ADDRESS which + * is case-sensitive. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_bitcoin (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + unsigned int sc = 0; + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + if ('/' == c) + sc++; + if (sc < 3) + res[i] = (char) tolower ((int) c); + else + res[i] = c; + } + return res; +} + + +/** + * Normalize "payto://ilp/$NAME" + * URI in @a input. + * + * Converts to lower-case. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_ilp (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + res[i] = (char) tolower ((int) c); + } + return res; +} + + +char * +TALER_payto_normalize (const char *input) +{ + char *method; + const char *end; + char *ret; + + { + char *err; + + err = TALER_payto_validate (input); + if (NULL != err) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Malformed payto://-URI `%s': %s\n", + input, + err); + GNUNET_free (err); + return NULL; + } + } + method = TALER_payto_get_method (input); + if (NULL == method) + { + GNUNET_break (0); + return NULL; + } + end = strchr (input, '?'); + if (NULL == end) + end = &input[strlen (input)]; + if (0 == strcasecmp (method, + "x-taler-bank")) + ret = normalize_payto_x_taler_bank (end - input, + input); + else if (0 == strcasecmp (method, + "iban")) + ret = normalize_payto_iban (end - input, + input); + else if (0 == strcasecmp (method, + "upi")) + ret = normalize_payto_upi (end - input, + input); + else if (0 == strcasecmp (method, + "bitcoin")) + ret = normalize_payto_bitcoin (end - input, + input); + else if (0 == strcasecmp (method, + "ilp")) + ret = normalize_payto_ilp (end - input, + input); + else + ret = GNUNET_strndup (input, + end - input); + GNUNET_free (method); + return ret; +} + + +void +TALER_payto_hash (const char *payto, + struct TALER_PaytoHashP *h_payto) +{ + struct GNUNET_HashCode sha512; + + GNUNET_CRYPTO_hash (payto, + strlen (payto) + 1, + &sha512); + GNUNET_static_assert (sizeof (sha512) > sizeof (*h_payto)); + /* truncate */ + GNUNET_memcpy (h_payto, + &sha512, + sizeof (*h_payto)); +} + + +char * +TALER_reserve_make_payto (const char *exchange_url, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + char pub_str[sizeof (*reserve_pub) * 2]; + char *end; + bool is_http; + char *reserve_url; + + end = GNUNET_STRINGS_data_to_string ( + reserve_pub, + sizeof (*reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + if (0 == strncmp (exchange_url, + "http://", + strlen ("http://"))) + { + is_http = true; + exchange_url = &exchange_url[strlen ("http://")]; + } + else if (0 == strncmp (exchange_url, + "https://", + strlen ("https://"))) + { + is_http = false; + exchange_url = &exchange_url[strlen ("https://")]; + } + else + { + GNUNET_break (0); + return NULL; + } + /* exchange_url includes trailing '/' */ + GNUNET_asprintf (&reserve_url, + "payto://%s/%s%s", + is_http ? "taler-reserve-http" : "taler-reserve", + exchange_url, + pub_str); + return reserve_url; +} + + +/* end of payto.c */ |