/* This file is part of TALER 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 Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file payto.c * @brief Common utility functions for dealing with payto://-URIs * @author Florian Dold */ #include "platform.h" #include "taler_util.h" /** * Prefix of PAYTO URLs. */ #define PAYTO "payto://" /** * Extract the value under @a key from the URI parameters. * * @param payto_uri the URL to parse * @param search_key key to look for, including "=" * @return NULL if the @a key parameter is not found. * The caller should free the returned value. */ static char * payto_get_key (const char *payto_uri, const char *search_key) { const char *key; const char *value_start; const char *value_end; key = strchr (payto_uri, (unsigned char) '?'); if (NULL == key) return NULL; do { if (0 == strncasecmp (++key, search_key, strlen (search_key))) { value_start = strchr (key, (unsigned char) '='); if (NULL == value_start) return NULL; value_end = strchrnul (value_start, (unsigned char) '&'); return GNUNET_strndup (value_start + 1, value_end - value_start - 1); } } while ( (key = strchr (key, (unsigned char) '&')) ); return NULL; } char * TALER_payto_get_subject (const char *payto_uri) { return payto_get_key (payto_uri, "subject="); } char * TALER_payto_get_method (const char *payto_uri) { const char *start; const char *end; if (0 != strncasecmp (payto_uri, PAYTO, strlen (PAYTO))) return NULL; start = &payto_uri[strlen (PAYTO)]; end = strchr (start, (unsigned char) '/'); if (NULL == end) return NULL; return GNUNET_strndup (start, end - start); } 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; } host = &payto[strlen (PAYTO "x-taler-bank/")]; beg = strchr (host, '/'); if (NULL == beg) { GNUNET_break_op (0); return NULL; } beg++; /* now points to $ACCOUNT or $PATH */ nxt = strchr (beg, '/'); end = strchr (beg, '?'); if (NULL == end) end = &beg[strlen (beg)]; while ( (NULL != nxt) && (end - nxt > 0) ) { beg = nxt + 1; nxt = strchr (beg, '/'); } return GNUNET_strndup (beg, end - beg); } /** * Validate payto://iban/ account URL (only account information, * wire subject and amount are ignored). * * @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_iban (const char *account_url) { const char *iban; const char *q; char *result; char *err; #define IBAN_PREFIX "payto://iban/" if (0 != strncasecmp (account_url, IBAN_PREFIX, strlen (IBAN_PREFIX))) return NULL; /* not an IBAN */ iban = strrchr (account_url, '/') + 1; #undef IBAN_PREFIX q = strchr (iban, '?'); if (NULL != q) { result = GNUNET_strndup (iban, q - iban); } else { result = GNUNET_strdup (iban); } if (NULL != (err = TALER_iban_validate (result))) { GNUNET_free (result); return err; } GNUNET_free (result); { 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; } /** * Validate payto://x-taler-bank/ account URL (only account information, * wire subject and amount are ignored). * * @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) { char *ret; const char *start; const char *end; if (0 != strncasecmp (payto_uri, PAYTO, strlen (PAYTO))) return GNUNET_strdup ("invalid prefix"); for (unsigned int i = 0; '\0' != payto_uri[i]; i++) { /* 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/:&?-.,=+%~" if (NULL == strchr (ALLOWED_CHARACTERS, (int) payto_uri[i])) { char *ret; GNUNET_asprintf (&ret, "Encountered invalid character `%c' at offset %u in payto URI `%s'", payto_uri[i], i, payto_uri); return ret; } #undef ALLOWED_CHARACTERS } start = &payto_uri[strlen (PAYTO)]; end = strchr (start, (unsigned char) '/'); if (NULL == end) return GNUNET_strdup ("missing '/' in payload"); 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 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 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 */