diff options
Diffstat (limited to 'src')
42 files changed, 752 insertions, 539 deletions
diff --git a/src/auditor/generate-auditor-basedb.sh b/src/auditor/generate-auditor-basedb.sh index 65aae3134..bbce37cdc 100755 --- a/src/auditor/generate-auditor-basedb.sh +++ b/src/auditor/generate-auditor-basedb.sh @@ -119,7 +119,6 @@ taler-wallet-cli \ echo " DONE" taler-wallet-cli --wallet-db="$WALLET_DB" run-until-done -taler-wallet-cli --wallet-db="$WALLET_DB" advanced run-pending # Dump database mkdir -p "$(dirname "$BASEDB")" diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c index d9cdff8d2..59bd849bc 100644 --- a/src/auditor/taler-auditor-httpd.c +++ b/src/auditor/taler-auditor-httpd.c @@ -169,7 +169,7 @@ handle_config (struct TAH_RequestHandler *rh, GNUNET_JSON_pack_string ("version", AUDITOR_PROTOCOL_VERSION), GNUNET_JSON_pack_string ("implementation", - "urn:net:taler:specs:auditor:c-reference"), + "urn:net:taler:specs:taler-auditor:c-reference"), GNUNET_JSON_pack_string ("currency", TAH_currency), GNUNET_JSON_pack_data_auto ("auditor_public_key", diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh index ea8637cfa..2cfea0532 100755 --- a/src/auditor/test-auditor.sh +++ b/src/auditor/test-auditor.sh @@ -166,7 +166,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-aggregation.out" \ 2> "${MY_TMP_DIR}/test-audit-aggregation.err" \ - || exit_fail "aggregation audit failed" + || exit_fail "aggregation audit failed (see ${MY_TMP_DIR}/test-audit-aggregation.*)" echo -n "." $VALGRIND taler-helper-auditor-aggregation \ -L DEBUG \ @@ -174,7 +174,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-aggregation-inc.out" \ 2> "${MY_TMP_DIR}/test-audit-aggregation-inc.err" \ - || exit_fail "incremental aggregation audit failed" + || exit_fail "incremental aggregation audit failed (see ${MY_TMP_DIR}/test-audit-aggregation-inc.*)" echo -n "." $VALGRIND taler-helper-auditor-coins \ -L DEBUG \ @@ -182,7 +182,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-coins.out" \ 2> "${MY_TMP_DIR}/test-audit-coins.err" \ - || exit_fail "coin audit failed" + || exit_fail "coin audit failed (see ${MY_TMP_DIR}/test-audit-coins.*)" echo -n "." $VALGRIND taler-helper-auditor-coins \ -L DEBUG \ @@ -190,7 +190,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-coins-inc.out" \ 2> "${MY_TMP_DIR}/test-audit-coins-inc.err" \ - || exit_fail "incremental coin audit failed" + || exit_fail "incremental coin audit failed (see ${MY_TMP_DIR}/test-audit-coins-inc.*)" echo -n "." $VALGRIND taler-helper-auditor-deposits \ -L DEBUG \ @@ -198,7 +198,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-deposits.out" \ 2> "${MY_TMP_DIR}/test-audit-deposits.err" \ - || exit_fail "deposits audit failed" + || exit_fail "deposits audit failed (see ${MY_TMP_DIR}/test-audit-deposits.*)" echo -n "." $VALGRIND taler-helper-auditor-deposits \ -L DEBUG \ @@ -206,7 +206,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-deposits-inc.out" \ 2> "${MY_TMP_DIR}/test-audit-deposits-inc.err" \ - || exit_fail "incremental deposits audit failed" + || exit_fail "incremental deposits audit failed (see ${MY_TMP_DIR}/test-audit-deposits-inc.*)" echo -n "." $VALGRIND taler-helper-auditor-reserves \ -i \ @@ -215,7 +215,7 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-reserves.out" \ 2> "${MY_TMP_DIR}/test-audit-reserves.err" \ - || exit_fail "reserves audit failed" + || exit_fail "reserves audit failed (see ${MY_TMP_DIR}/test-audit-reserves.*)" echo -n "." $VALGRIND taler-helper-auditor-reserves \ -i \ @@ -224,25 +224,25 @@ function audit_only () { -t \ > "${MY_TMP_DIR}/test-audit-reserves-inc.out" \ 2> "${MY_TMP_DIR}/test-audit-reserves-inc.err" \ - || exit_fail "incremental reserves audit failed" + || exit_fail "incremental reserves audit failed (see ${MY_TMP_DIR}/test-audit-reserves-inc.*)" echo -n "." $VALGRIND taler-helper-auditor-wire \ -i \ -L DEBUG \ -c "$CONF" \ -t \ - > "${MY_TMP_DIR}/test-wire-audit.out" \ - 2> "${MY_TMP_DIR}/test-wire-audit.err" \ - || exit_fail "wire audit failed" + > "${MY_TMP_DIR}/test-audit-wire.out" \ + 2> "${MY_TMP_DIR}/test-audit-wire.err" \ + || exit_fail "wire audit failed (see ${MY_TMP_DIR}/test-audit-wire.*)" echo -n "." $VALGRIND taler-helper-auditor-wire \ -i \ -L DEBUG \ -c "$CONF" \ -t \ - > "${MY_TMP_DIR}/test-wire-audit-inc.out" \ - 2> "${MY_TMP_DIR}/test-wire-audit-inc.err" \ - || exit_fail "wire audit inc failed" + > "${MY_TMP_DIR}/test-audit-wire-inc.out" \ + 2> "${MY_TMP_DIR}/test-audit-wire-inc.err" \ + || exit_fail "wire audit inc failed (see ${MY_TMP_DIR}/test-audit-wire-inc.*)" echo -n "." echo " DONE" @@ -2211,32 +2211,45 @@ grep -v host \ > "$TMPDIR/pg_hba.conf.new" mv "$TMPDIR/pg_hba.conf.new" "$TMPDIR/pg_hba.conf" "${POSTGRES_PATH}/pg_ctl" \ - -D "$TMPDIR" \ - -l "${MY_TMP_DIR}/postgres.log" \ - start \ - > "${MY_TMP_DIR}/postgres-start.log" \ - 2> "${MY_TMP_DIR}/postgres-start.err" + -D "$TMPDIR" \ + -l "${MY_TMP_DIR}/postgres.log" \ + start \ + > "${MY_TMP_DIR}/postgres-start.log" \ + 2> "${MY_TMP_DIR}/postgres-start.err" echo " DONE" PGHOST="$TMPDIR/sockets" export PGHOST MYDIR="${MY_TMP_DIR}/basedb" mkdir -p "${MYDIR}" -echo "Generating fresh database at $MYDIR" -if faketime -f '-1 d' ./generate-auditor-basedb.sh -d "$MYDIR/$DB" +if [ -z $REUSE_BASEDB_DIR ] then - echo -n "Reset 'auditor-basedb' database at $PGHOST ..." - dropdb "auditor-basedb" >/dev/null 2>/dev/null || true - createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at $PGHOST" - echo " DONE" - check_with_database "$MYDIR/$DB" - if [ "$fail" != "0" ] + echo "Generating fresh database at $MYDIR" + + if faketime -f '-1 d' ./generate-auditor-basedb.sh -d "$MYDIR/$DB" then - exit "$fail" + echo -n "Reset 'auditor-basedb' database at $PGHOST ..." + dropdb "auditor-basedb" >/dev/null 2>/dev/null || true + createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at $PGHOST" + echo " DONE" + else + echo "Generation failed" + exit 1 fi else - echo "Generation failed" - exit 1 + echo "Reusing existing database from ${REUSE_BASEDB_DIR}" + cp -r "${REUSE_BASEDB_DIR}/basedb"/* "${MYDIR}/" +fi + +check_with_database "$MYDIR/$DB" +if [ "$fail" != "0" ] +then + exit "$fail" +fi + +if [ -z $REUSE_BASEDB_DIR ] +then + echo "Run 'export REUSE_BASEDB_DIR=${MY_TMP_DIR}' to re-run tests against the same database" fi exit 0 diff --git a/src/auditordb/Makefile.am b/src/auditordb/Makefile.am index 3e7d83d09..c0282e9c9 100644 --- a/src/auditordb/Makefile.am +++ b/src/auditordb/Makefile.am @@ -31,12 +31,12 @@ CLEANFILES = \ auditor-0002.sql procedures.sql: procedures.sql.in auditor_do_*.sql - chmod +w $@ || true + chmod +w $@ 2> /dev/null || true gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ chmod ugo-w $@ auditor-0002.sql: auditor-0002.sql.in 0002-*.sql - chmod +w $@ || true + chmod +w $@ 2> /dev/null || true gcc -E -P -undef - < auditor-0002.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ chmod ugo-w $@ diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index b849db5d8..a292dcece 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -51,7 +51,7 @@ libtalerbank_la_LIBADD = \ -lgnunetjson \ -lgnunetutil \ -ljansson \ - $(LIBGNURLCURL_LIBS) \ + -lcurl \ $(XLIB) libtalerfakebank_la_LDFLAGS = \ @@ -94,7 +94,7 @@ libtalerfakebank_la_LIBADD = \ -lgnunetjson \ -lgnunetutil \ -ljansson \ - $(LIBGNURLCURL_LIBS) \ + -lcurl \ -lmicrohttpd \ -lpthread \ $(XLIB) diff --git a/src/curl/Makefile.am b/src/curl/Makefile.am index f60a3806d..c8f8761aa 100644 --- a/src/curl/Makefile.am +++ b/src/curl/Makefile.am @@ -17,7 +17,7 @@ libtalercurl_la_SOURCES = \ libtalercurl_la_LIBADD = \ -lgnunetcurl \ -lgnunetutil \ - $(LIBGNURLCURL_LIBS) \ + -lcurl \ -ljansson \ -lz \ -lm \ diff --git a/src/exchange/taler-exchange-httpd_config.c b/src/exchange/taler-exchange-httpd_config.c index 29cbf2b51..257dfa6ba 100644 --- a/src/exchange/taler-exchange-httpd_config.c +++ b/src/exchange/taler-exchange-httpd_config.c @@ -33,9 +33,31 @@ TEH_handler_config (struct TEH_RequestContext *rc, const char *const args[]) { static struct MHD_Response *resp; + static struct GNUNET_TIME_Absolute a; + (void) args; + if ( (GNUNET_TIME_absolute_is_past (a)) && + (NULL != resp) ) + { + MHD_destroy_response (resp); + resp = NULL; + } if (NULL == resp) { + struct GNUNET_TIME_Timestamp km; + char dat[128]; + + a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + GNUNET_TIME_UNIT_DAYS); + a = GNUNET_TIME_absolute_add (a, + GNUNET_TIME_UNIT_DAYS); + /* => /config response stays at most 48h in caches! */ + km = GNUNET_TIME_absolute_to_timestamp (a); + TALER_MHD_get_date_string (km.abs_time, + dat); resp = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_array_steal ("supported_kyc_requirements", TALER_KYCLOGIC_get_satisfiable ()), @@ -47,9 +69,19 @@ TEH_handler_config (struct TEH_RequestContext *rc, GNUNET_JSON_pack_string ("name", "taler-exchange"), GNUNET_JSON_pack_string ("implementation", - "urn:net:taler:specs:exchange:c-reference"), + "urn:net:taler:specs:taler-exchange:c-reference") + , GNUNET_JSON_pack_string ("version", EXCHANGE_PROTOCOL_VERSION)); + + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_EXPIRES, + dat)); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,max-age=21600")); /* 6h */ } return MHD_queue_response (rc->connection, MHD_HTTP_OK, diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h index 40293e89a..068f51d41 100644 --- a/src/exchange/taler-exchange-httpd_config.h +++ b/src/exchange/taler-exchange-httpd_config.h @@ -41,7 +41,7 @@ * * Returned via both /config and /keys endpoints. */ -#define EXCHANGE_PROTOCOL_VERSION "18:1:1" +#define EXCHANGE_PROTOCOL_VERSION "19:2:2" /** diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 05fb685e0..0ec28e950 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -2273,12 +2273,10 @@ setup_general_response_headers (void *cls, MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json")); - TALER_MHD_get_date_string (ksh->reload_time.abs_time, - dat); GNUNET_break (MHD_YES == MHD_add_response_header (response, - MHD_HTTP_HEADER_LAST_MODIFIED, - dat)); + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,must-revalidate,max-age=86400")); if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency)) { struct GNUNET_TIME_Relative r; @@ -2290,6 +2288,12 @@ setup_general_response_headers (void *cls, r = GNUNET_TIME_relative_min (TEH_max_keys_caching, ksh->rekey_frequency); a = GNUNET_TIME_relative_to_absolute (r); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + GNUNET_TIME_UNIT_DAYS); + a = GNUNET_TIME_absolute_add (a, + GNUNET_TIME_UNIT_DAYS); km = GNUNET_TIME_absolute_to_timestamp (a); we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration); m = GNUNET_TIME_timestamp_min (we, @@ -2314,11 +2318,6 @@ setup_general_response_headers (void *cls, MHD_add_response_header (response, MHD_HTTP_HEADER_VARY, MHD_HTTP_HEADER_ACCEPT_ENCODING)); - /* Information is always public, revalidate after 1 hour */ - GNUNET_break (MHD_YES == - MHD_add_response_header (response, - MHD_HTTP_HEADER_CACHE_CONTROL, - "public,max-age=3600")); } @@ -2596,6 +2595,7 @@ create_krd (struct TEH_KeyStateHandle *ksh, GNUNET_assert (NULL != krd.response_uncompressed); setup_general_response_headers (ksh, krd.response_uncompressed); + /* Information is always public, revalidate after 1 day */ GNUNET_break (MHD_YES == MHD_add_response_header (krd.response_uncompressed, MHD_HTTP_HEADER_ETAG, @@ -2617,18 +2617,9 @@ create_krd (struct TEH_KeyStateHandle *ksh, "deflate")) ); setup_general_response_headers (ksh, krd.response_compressed); - /* Set cache control headers: our response varies depending on these headers */ - GNUNET_break (MHD_YES == - MHD_add_response_header (krd.response_compressed, - MHD_HTTP_HEADER_VARY, - MHD_HTTP_HEADER_ACCEPT_ENCODING)); /* Information is always public, revalidate after 1 day */ GNUNET_break (MHD_YES == MHD_add_response_header (krd.response_compressed, - MHD_HTTP_HEADER_CACHE_CONTROL, - "public,max-age=86400")); - GNUNET_break (MHD_YES == - MHD_add_response_header (krd.response_compressed, MHD_HTTP_HEADER_ETAG, etag)); krd.etag = GNUNET_strdup (etag); diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c index b31078f00..ac3902e3f 100644 --- a/src/exchange/taler-exchange-httpd_melt.c +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -58,6 +58,7 @@ reply_melt_success (struct MHD_Connection *connection, &pub, &sig))) { + GNUNET_break (0); return TALER_MHD_reply_with_ec (connection, ec, NULL); diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c index 33ead7c69..b8bcf7c60 100644 --- a/src/exchange/taler-exchange-httpd_refund.c +++ b/src/exchange/taler-exchange-httpd_refund.c @@ -158,6 +158,7 @@ refund_transaction (void *cls, } if (conflict) { + GNUNET_break_op (0); *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT, diff --git a/src/exchange/taler-exchange-httpd_transfers_get.c b/src/exchange/taler-exchange-httpd_transfers_get.c index 2a6dc8776..18d96f955 100644 --- a/src/exchange/taler-exchange-httpd_transfers_get.c +++ b/src/exchange/taler-exchange-httpd_transfers_get.c @@ -59,14 +59,20 @@ struct AggregatedDepositDetail struct TALER_CoinSpendPublicKeyP coin_pub; /** - * Total value of the coin in the deposit. + * Total value of the coin in the deposit (after + * refunds). */ struct TALER_Amount deposit_value; /** - * Fees charged by the exchange for the deposit of this coin. + * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds). */ struct TALER_Amount deposit_fee; + + /** + * Total amount refunded for this coin. + */ + struct TALER_Amount refund_total; }; @@ -120,6 +126,13 @@ reply_transfer_details (struct MHD_Connection *connection, &wdd_pos->h_contract_terms), GNUNET_JSON_pack_data_auto ("coin_pub", &wdd_pos->coin_pub), + + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("refund_total", + TALER_amount_is_zero ( + &wdd_pos->refund_total) + ? NULL + : &wdd_pos->refund_total)), TALER_JSON_pack_amount ("deposit_value", &wdd_pos->deposit_value), TALER_JSON_pack_amount ("deposit_fee", @@ -248,6 +261,31 @@ struct WtidTransactionContext /** + * Callback that totals up the applicable refunds. + * + * @param cls a `struct TALER_Amount` where we keep the total + * @param amount_with_fee amount being refunded + */ +static enum GNUNET_GenericReturnValue +add_refunds (void *cls, + const struct TALER_Amount *amount_with_fee) + +{ + struct TALER_Amount *total = cls; + + if (0 > + TALER_amount_add (total, + total, + amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** * Function called with the results of the lookup of the individual deposits * that were aggregated for the given wire transfer. * @@ -260,29 +298,96 @@ struct WtidTransactionContext * @param h_contract_terms which proposal was this payment about * @param denom_pub denomination public key of the @a coin_pub (ignored) * @param coin_pub which public key was this payment about - * @param deposit_value amount contributed by this coin in total + * @param deposit_value amount contributed by this coin in total (including fee) * @param deposit_fee deposit fee charged by exchange for this coin */ static void -handle_deposit_data (void *cls, - uint64_t rowid, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const char *account_payto_uri, - const struct TALER_PaytoHashP *h_payto, - struct GNUNET_TIME_Timestamp exec_time, - const struct TALER_PrivateContractHashP *h_contract_terms, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *deposit_value, - const struct TALER_Amount *deposit_fee) +handle_deposit_data ( + void *cls, + uint64_t rowid, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *account_payto_uri, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Timestamp exec_time, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *deposit_value, + const struct TALER_Amount *deposit_fee) { struct WtidTransactionContext *ctx = cls; + struct TALER_Amount total_refunds; + struct TALER_Amount dval; + struct TALER_Amount dfee; + enum GNUNET_DB_QueryStatus qs; (void) rowid; (void) denom_pub; (void) h_payto; if (GNUNET_SYSERR == ctx->is_valid) return; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (deposit_value->currency, + &total_refunds)); + qs = TEH_plugin->select_refunds_by_coin (TEH_plugin->cls, + coin_pub, + merchant_pub, + h_contract_terms, + &add_refunds, + &total_refunds); + if (qs < 0) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + if (1 == + TALER_amount_cmp (&total_refunds, + deposit_value)) + { + /* Refunds exceeded total deposit? not OK! */ + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + if (0 == + TALER_amount_cmp (&total_refunds, + deposit_value)) + { + /* total_refunds == deposit_value; + in this case, the total contributed to the + wire transfer is zero (as are fees) */ + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (deposit_value->currency, + &dval)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (deposit_value->currency, + &dfee)); + + } + else + { + /* Compute deposit value by subtracting refunds */ + GNUNET_assert (0 < + TALER_amount_subtract (&dval, + deposit_value, + &total_refunds)); + if (-1 == + TALER_amount_cmp (&dval, + deposit_fee)) + { + /* dval < deposit_fee, so after refunds less than + the deposit fee remains; reduce deposit fee to + the remaining value of the coin */ + dfee = dval; + } + else + { + /* Partial refund, deposit fee remains */ + dfee = *deposit_fee; + } + } + if (GNUNET_NO == ctx->is_valid) { /* First one we encounter, setup general information in 'ctx' */ @@ -292,8 +397,8 @@ handle_deposit_data (void *cls, ctx->is_valid = GNUNET_YES; if (0 > TALER_amount_subtract (&ctx->total, - deposit_value, - deposit_fee)) + &dval, + &dfee)) { GNUNET_break (0); ctx->is_valid = GNUNET_SYSERR; @@ -317,8 +422,8 @@ handle_deposit_data (void *cls, } if (0 > TALER_amount_subtract (&delta, - deposit_value, - deposit_fee)) + &dval, + &dfee)) { GNUNET_break (0); ctx->is_valid = GNUNET_SYSERR; @@ -339,8 +444,9 @@ handle_deposit_data (void *cls, struct AggregatedDepositDetail *wdd; wdd = GNUNET_new (struct AggregatedDepositDetail); - wdd->deposit_value = *deposit_value; - wdd->deposit_fee = *deposit_fee; + wdd->deposit_value = dval; + wdd->deposit_fee = dfee; + wdd->refund_total = total_refunds; wdd->h_contract_terms = *h_contract_terms; wdd->coin_pub = *coin_pub; GNUNET_CONTAINER_DLL_insert (ctx->wdd_head, diff --git a/src/exchangedb/0002-kyc_attributes.sql b/src/exchangedb/0002-kyc_attributes.sql index 897aff561..66f3fc315 100644 --- a/src/exchangedb/0002-kyc_attributes.sql +++ b/src/exchangedb/0002-kyc_attributes.sql @@ -107,16 +107,6 @@ BEGIN ' ADD CONSTRAINT ' || table_name || '_serial_key ' 'UNIQUE (kyc_attributes_serial_id)' ); - -- The legitimization_serial is a foreign key. - -- But, due to partitioning by h_payto, we can not simply reference - -- the serial id of the legitimization_processes. Thus, the following - -- is commented out. - -- EXECUTE FORMAT ( - -- 'ALTER TABLE ' || table_name || - -- ' ADD CONSTRAINT ' || table_name || '_foreign_legitimization_processes' - -- ' FOREIGN KEY (legitimization_serial) ' - -- ' REFERENCES legitimization_processes (legitimization_process_serial_id)' -- ON DELETE CASCADE - -- ); -- To search similar users (e.g. during AML checks) EXECUTE FORMAT ( 'CREATE INDEX ' || table_name || '_similarity_index ' diff --git a/src/exchangedb/0004-refunds.sql b/src/exchangedb/0004-refunds.sql new file mode 100644 index 000000000..eb9e7ad6e --- /dev/null +++ b/src/exchangedb/0004-refunds.sql @@ -0,0 +1,35 @@ + +CREATE FUNCTION constrain_table_refunds4 ( + IN partition_suffix TEXT DEFAULT NULL +) +RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE + table_name TEXT DEFAULT 'refunds'; +BEGIN + table_name = concat_ws('_', table_name, partition_suffix); + + EXECUTE FORMAT ( + 'ALTER TABLE ' || table_name || + ' DROP CONSTRAINT ' || table_name || '_pkey' + ); + EXECUTE FORMAT ( + 'ALTER TABLE ' || table_name || + ' ADD PRIMARY KEY (batch_deposit_serial_id, coin_pub, rtransaction_id) ' + ); +END +$$; + +INSERT INTO exchange_tables + (name + ,version + ,action + ,partitioned + ,by_range) + VALUES + ('refunds4' + ,'exchange-0004' + ,'constrain' + ,TRUE + ,FALSE); diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 45070ac08..fd993f968 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -19,8 +19,10 @@ sqlinputs = \ procedures.sql.in \ 0002-*.sql \ 0003-*.sql \ + 0004-*.sql \ exchange-0002.sql.in \ - exchange-0003.sql.in + exchange-0003.sql.in \ + exchange-0004.sql.in sql_DATA = \ benchmark-0001.sql \ @@ -29,6 +31,7 @@ sql_DATA = \ exchange-0001.sql \ exchange-0002.sql \ exchange-0003.sql \ + exchange-0004.sql \ drop.sql \ procedures.sql @@ -44,20 +47,25 @@ CLEANFILES = \ procedures.sql procedures.sql: procedures.sql.in exchange_do_*.sql - chmod +w $@ || true + chmod +w $@ 2> /dev/null || true gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ chmod ugo-w $@ exchange-0002.sql: exchange-0002.sql.in 0002-*.sql - chmod +w $@ || true + chmod +w $@ 2> /dev/null || true gcc -E -P -undef - < exchange-0002.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ chmod ugo-w $@ exchange-0003.sql: exchange-0003.sql.in 0003-*.sql - chmod +w $@ || true + chmod +w $@ 2> /dev/null || true gcc -E -P -undef - < exchange-0003.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ chmod ugo-w $@ +exchange-0004.sql: exchange-0004.sql.in 0004-*.sql + chmod +w $@ 2> /dev/null || true + gcc -E -P -undef - < exchange-0004.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ + chmod ugo-w $@ + check_SCRIPTS = \ test_idempotency.sh diff --git a/src/exchangedb/exchange-0004.sql.in b/src/exchangedb/exchange-0004.sql.in new file mode 100644 index 000000000..c966aedc5 --- /dev/null +++ b/src/exchangedb/exchange-0004.sql.in @@ -0,0 +1,24 @@ +-- +-- This file is part of TALER +-- Copyright (C) 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 <http://www.gnu.org/licenses/> +-- + +BEGIN; + +SELECT _v.register_patch('exchange-0004', NULL, NULL); +SET search_path TO exchange; + +#include "0004-refunds.sql" + +COMMIT; diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index e3ae829fd..b941316b5 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -1336,15 +1336,21 @@ struct TALER_TrackTransferDetails struct TALER_CoinSpendPublicKeyP coin_pub; /** - * Value of the deposit (including fee). + * Value of the deposit (including fee), after refunds. */ struct TALER_Amount coin_value; /** - * Fee charged by the exchange for the deposit. + * Fee charged by the exchange for the deposit, + * possibly reduced (or waived) due to refunds. */ struct TALER_Amount coin_fee; + /** + * Total amount of refunds applied to this coin. + */ + struct TALER_Amount refund_total; + }; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 985664c43..2d5857677 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4483,14 +4483,13 @@ struct TALER_EXCHANGEDB_Plugin * @return query result status */ enum GNUNET_DB_QueryStatus - (*select_refunds_by_coin)(void *cls, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct - TALER_MerchantPublicKeyP *merchant_pub, - const struct - TALER_PrivateContractHashP *h_contract, - TALER_EXCHANGEDB_RefundCoinCallback cb, - void *cb_cls); + (*select_refunds_by_coin)( + void *cls, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_PrivateContractHashP *h_contract, + TALER_EXCHANGEDB_RefundCoinCallback cb, + void *cb_cls); /** diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h index d93bc1e14..57d041758 100644 --- a/src/include/taler_mhd_lib.h +++ b/src/include/taler_mhd_lib.h @@ -173,8 +173,8 @@ TALER_MHD_reply_json_pack (struct MHD_Connection *connection, * @return MHD result code */ #define TALER_MHD_REPLY_JSON_PACK(connection,response_code,...) \ - TALER_MHD_reply_json_steal (connection, GNUNET_JSON_PACK (__VA_ARGS__), \ - response_code) + TALER_MHD_reply_json_steal (connection, GNUNET_JSON_PACK (__VA_ARGS__), \ + response_code) /** @@ -259,7 +259,7 @@ TALER_MHD_make_json_pack (const char *fmt, * @return MHD response object */ #define TALER_MHD_MAKE_JSON_PACK(...) \ - TALER_MHD_make_json_steal (GNUNET_JSON_PACK (__VA_ARGS__)) + TALER_MHD_make_json_steal (GNUNET_JSON_PACK (__VA_ARGS__)) /** @@ -270,8 +270,8 @@ TALER_MHD_make_json_pack (const char *fmt, * @return packer array entries (two!) */ #define TALER_MHD_PACK_EC(ec) \ - GNUNET_JSON_pack_uint64 ("code", ec), \ - GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)) + GNUNET_JSON_pack_uint64 ("code", ec), \ + GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)) /** * Create a response indicating an internal error. @@ -462,19 +462,19 @@ TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection, * the current time plus the value given under "timeout_ms" otherwise */ #define TALER_MHD_parse_request_timeout(connection,expiration) \ - do { \ - switch (TALER_MHD_parse_request_arg_timeout (connection, \ - expiration)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - case GNUNET_OK: \ - break; \ - } \ - } while (0) + do { \ + switch (TALER_MHD_parse_request_arg_timeout (connection, \ + expiration)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) /** @@ -505,20 +505,20 @@ TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection, * unchanged if value was not specified */ #define TALER_MHD_parse_request_number(connection,name,off) \ - do { \ - switch (TALER_MHD_parse_request_arg_number (connection, \ - name, \ - off)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - case GNUNET_OK: \ - break; \ - } \ - } while (0) + do { \ + switch (TALER_MHD_parse_request_arg_number (connection, \ + name, \ + off)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) /** @@ -549,20 +549,20 @@ TALER_MHD_parse_request_arg_snumber (struct MHD_Connection *connection, * unchanged if value was not specified */ #define TALER_MHD_parse_request_snumber(connection,name,val) \ - do { \ - switch (TALER_MHD_parse_request_arg_snumber (connection, \ - name, \ - val)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - case GNUNET_OK: \ - break; \ - } \ - } while (0) + do { \ + switch (TALER_MHD_parse_request_arg_snumber (connection, \ + name, \ + val)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) /** @@ -593,20 +593,42 @@ TALER_MHD_parse_request_arg_amount (struct MHD_Connection *connection, * unchanged if value was not specified */ #define TALER_MHD_parse_request_amount(connection,name,val) \ - do { \ - switch (TALER_MHD_parse_request_arg_amount (connection, \ - name, \ - val)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - case GNUNET_OK: \ - break; \ - } \ - } while (0) + do { \ + switch (TALER_MHD_parse_request_arg_amount (connection, \ + name, \ + val)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Determines which of the given choices is preferred + * by the client. Parses an HTTP header such as + * "Accept:" or "Language:" to find entries matching + * the list of strings given in the variadic argument + * list. Returns the index of the preferred choice. + * + * @param connection HTTP request handle + * @param header type of HTTP header to evaluate + * @param ... NULL-terminated list of choices to + * check for in the header + * @return -1 if none of the given choices is in + * the header, -2 if the header is missing, + * otherwise index of preferred choice in + * the varargs list + */ +int +TALER_MHD_check_accept (struct MHD_Connection *connection, + const char *header, + ...); /** @@ -670,28 +692,28 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * #GNUNET_SYSERR on internal error (error response could not be sent) */ #define TALER_MHD_parse_request_arg_auto(connection,name,val,required) \ - do { \ - bool p; \ - switch (TALER_MHD_parse_request_arg_data (connection, name, \ - val, sizeof (*val), &p)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - return MHD_YES; \ - case GNUNET_OK: \ - if (required & (! p)) \ - return TALER_MHD_reply_with_error ( \ - connection, \ - MHD_HTTP_BAD_REQUEST, \ - TALER_EC_GENERIC_PARAMETER_MISSING, \ - name); \ - required = p; \ - break; \ - } \ - } while (0) + do { \ + bool p; \ + switch (TALER_MHD_parse_request_arg_data (connection, name, \ + val, sizeof (*val), &p)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ + if (required & (! p)) \ + return TALER_MHD_reply_with_error ( \ + connection, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MISSING, \ + name); \ + required = p; \ + break; \ + } \ + } while (0) /** @@ -706,10 +728,10 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * #GNUNET_SYSERR on internal error (error response could not be sent) */ #define TALER_MHD_parse_request_arg_auto_t(connection,name,val) \ - do { \ - bool b = true; \ - TALER_MHD_parse_request_arg_auto (connection,name,val,b); \ - } while (0) + do { \ + bool b = true; \ + TALER_MHD_parse_request_arg_auto (connection,name,val,b); \ + } while (0) /** * Extract fixed-size base32crockford encoded data from request. @@ -725,28 +747,28 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * #GNUNET_SYSERR on internal error (error response could not be sent) */ #define TALER_MHD_parse_request_header_auto(connection,name,val,required) \ - do { \ - bool p; \ - switch (TALER_MHD_parse_request_header_data (connection, name, \ - val, sizeof (*val), &p)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - return MHD_YES; \ - case GNUNET_OK: \ - if (required & (! p)) \ - return TALER_MHD_reply_with_error ( \ - connection, \ - MHD_HTTP_BAD_REQUEST, \ - TALER_EC_GENERIC_PARAMETER_MISSING, \ - name); \ - required = p; \ - break; \ - } \ - } while (0) + do { \ + bool p; \ + switch (TALER_MHD_parse_request_header_data (connection, name, \ + val, sizeof (*val), &p)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ + if (required & (! p)) \ + return TALER_MHD_reply_with_error ( \ + connection, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MISSING, \ + name); \ + required = p; \ + break; \ + } \ + } while (0) /** @@ -761,10 +783,10 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * #GNUNET_SYSERR on internal error (error response could not be sent) */ #define TALER_MHD_parse_request_header_auto_t(connection,name,val) \ - do { \ - bool b = true; \ - TALER_MHD_parse_request_header_auto (connection,name,val,b); \ - } while (0) + do { \ + bool b = true; \ + TALER_MHD_parse_request_header_auto (connection,name,val,b); \ + } while (0) /** @@ -795,19 +817,19 @@ TALER_MHD_check_content_length_ (struct MHD_Connection *connection, * @param max_len maximum allowed content length */ #define TALER_MHD_check_content_length(connection,max_len) \ - do { \ - switch (TALER_MHD_check_content_length_ (connection, max_len)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - return MHD_YES; \ - case GNUNET_OK: \ - break; \ - } \ - } while (0) + do { \ + switch (TALER_MHD_check_content_length_ (connection, max_len)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) /** diff --git a/src/include/taler_pq_lib.h b/src/include/taler_pq_lib.h index 6fae8562a..f45de61d9 100644 --- a/src/include/taler_pq_lib.h +++ b/src/include/taler_pq_lib.h @@ -422,31 +422,6 @@ TALER_PQ_result_spec_array_amount ( struct TALER_Amount **amounts); -/** - * Blind sign public key expected. - * - * @param name name of the field in the table - * @param[out] public_key where to store the denomination signature - * @return array entry for the result specification to use - */ -struct GNUNET_PQ_ResultSpec -TALER_PQ_result_spec_blind_sign_pub ( - const char *name, - struct GNUNET_CRYPTO_BlindSignPublicKey *public_key); - - -/** - * Blind sign private key expected. - * - * @param name name of the field in the table - * @param[out] private_key where to store the denomination signature - * @return array entry for the result specification to use - */ -struct GNUNET_PQ_ResultSpec -TALER_PQ_result_spec_blind_sign_priv ( - const char *name, - struct GNUNET_CRYPTO_BlindSignPrivateKey *private_key); - #endif /* TALER_PQ_LIB_H_ */ /* end of include/taler_pq_lib.h */ diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 256efd11d..63dab7c80 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -18,7 +18,7 @@ lib_LTLIBRARIES = \ libtalerexchange.la libtalerexchange_la_LDFLAGS = \ - -version-info 6:0:0 \ + -version-info 7:0:0 \ -no-undefined libtalerexchange_la_SOURCES = \ exchange_api_add_aml_decision.c \ @@ -87,8 +87,8 @@ libtalerexchange_la_LIBADD = \ -lgnunetjson \ -lgnunetutil \ -ljansson \ + -lcurl \ -lm \ - $(LIBGNURLCURL_LIBS) \ $(XLIB) libtalerauditor_la_LDFLAGS = \ @@ -106,8 +106,8 @@ libtalerauditor_la_LIBADD = \ -lgnunetjson \ -lgnunetutil \ -ljansson \ + -lcurl \ -lm \ - $(LIBGNURLCURL_LIBS) \ $(XLIB) diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index bce3f2876..fdadc8d2a 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -40,12 +40,12 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define EXCHANGE_PROTOCOL_CURRENT 17 +#define EXCHANGE_PROTOCOL_CURRENT 19 /** * How many versions are we backwards compatible with? */ -#define EXCHANGE_PROTOCOL_AGE 0 +#define EXCHANGE_PROTOCOL_AGE 2 /** * Set to 1 for extra debug logging. diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c index 7401bfe4f..9159b55f2 100644 --- a/src/lib/exchange_api_refund.c +++ b/src/lib/exchange_api_refund.c @@ -323,6 +323,10 @@ handle_refund_finished (void *cls, rr.hr.ec = TALER_JSON_get_error_code (j); rr.hr.hint = TALER_JSON_get_error_hint (j); break; + case MHD_HTTP_FAILED_DEPENDENCY: + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; case MHD_HTTP_PRECONDITION_FAILED: if (GNUNET_OK != verify_failed_dependency_ok (rh, diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c index 14cf51ff9..c558fb42e 100644 --- a/src/lib/exchange_api_transfers_get.c +++ b/src/lib/exchange_api_transfers_get.c @@ -153,21 +153,27 @@ check_transfers_get_response_ok ( GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &detail->h_contract_terms), GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), - TALER_JSON_spec_amount_any ("deposit_value", &detail->coin_value), - TALER_JSON_spec_amount_any ("deposit_fee", &detail->coin_fee), + TALER_JSON_spec_amount ("deposit_value", + total_expected.currency, + &detail->coin_value), + TALER_JSON_spec_amount ("deposit_fee", + total_expected.currency, + &detail->coin_fee), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount ("refund_total", + total_expected.currency, + &detail->refund_total), + NULL), GNUNET_JSON_spec_end () }; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (td->total_amount.currency, + &detail->refund_total)); if ( (GNUNET_OK != GNUNET_JSON_parse (detail_j, spec_detail, NULL, NULL)) || - (GNUNET_OK != - TALER_amount_cmp_currency (&total_expected, - &detail->coin_value)) || - (GNUNET_OK != - TALER_amount_cmp_currency (&total_expected, - &detail->coin_fee)) || (0 > TALER_amount_add (&total_expected, &total_expected, diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am index d36bba42f..cd3fe7701 100644 --- a/src/mhd/Makefile.am +++ b/src/mhd/Makefile.am @@ -16,7 +16,7 @@ libtalermhd_la_SOURCES = \ mhd_responses.c \ mhd_run.c libtalermhd_la_LDFLAGS = \ - -version-info 2:0:2 \ + -version-info 3:0:3 \ -no-undefined libtalermhd_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c index 5a600953e..8353a6901 100644 --- a/src/mhd/mhd_legal.c +++ b/src/mhd/mhd_legal.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2019, 2020, 2022 Taler Systems SA + Copyright (C) 2019, 2020, 2022, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -131,6 +131,7 @@ mime_matches (const char *accept_pattern, if ( (NULL == da) || (NULL == dm) ) return (0 == strcmp ("*", accept_pattern)); + // FIXME: use TALER_MHD_check_accept() here! /* FIXME: eventually, we might want to parse the "q=$FLOAT" part after the ';' and figure out which one is the best/preferred match instead of returning a boolean... */ @@ -187,6 +188,12 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING); m = GNUNET_TIME_absolute_to_timestamp (a); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + MAX_TERMS_CACHING); + a = GNUNET_TIME_absolute_add (a, + MAX_TERMS_CACHING); TALER_MHD_get_date_string (m.abs_time, dat); GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -351,7 +358,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn, GNUNET_break (MHD_YES == MHD_add_response_header (resp, MHD_HTTP_HEADER_CACHE_CONTROL, - "public max-age=864000")); + "public,max-age=864000")); if (NULL != legal) GNUNET_break (MHD_YES == MHD_add_response_header (resp, diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index 2c3312cf2..771319bd5 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -508,4 +508,66 @@ TALER_MHD_check_content_length_ (struct MHD_Connection *connection, } +int +TALER_MHD_check_accept (struct MHD_Connection *connection, + const char *header, + ...) +{ + bool ret = false; + const char *accept; + char *a; + char *saveptr; + + accept = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + header); + if (NULL == accept) + return -2; /* no Accept header set */ + + a = GNUNET_strdup (accept); + for (char *t = strtok_r (a, ",", &saveptr); + NULL != t; + t = strtok_r (NULL, ",", &saveptr)) + { + char *end; + + /* skip leading whitespace */ + while (isspace ((unsigned char) t[0])) + t++; + /* trim of ';q=' parameter and everything after space */ + /* FIXME: eventually, we might want to parse the "q=$FLOAT" + part after the ';' and figure out which one is the + best/preferred match instead of returning a boolean... */ + end = strchr (t, ';'); + if (NULL != end) + *end = '\0'; + end = strchr (t, ' '); + if (NULL != end) + *end = '\0'; + { + va_list ap; + int off = 0; + const char *val; + + va_start (ap, + header); + while (NULL != (val = va_arg (ap, + const char *))) + { + if (0 == strcasecmp (val, + t)) + { + ret = off; + break; + } + off++; + } + va_end (ap); + } + } + GNUNET_free (a); + return ret; +} + + /* end of mhd_parsing.c */ diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c index d25b3223e..b1dfd4cf1 100644 --- a/src/pq/pq_query_helper.c +++ b/src/pq/pq_query_helper.c @@ -1336,204 +1336,4 @@ TALER_PQ_query_param_array_amount_with_currency ( } -/** - * Function called to convert input argument into SQL parameters. - * - * @param cls closure - * @param data pointer to input argument - * @param data_len number of bytes in @a data (if applicable) - * @param[out] param_values SQL data to set - * @param[out] param_lengths SQL length data to set - * @param[out] param_formats SQL format data to set - * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays - * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() - * @param scratch_length number of entries left in @a scratch - * @return -1 on error, number of offsets used in @a scratch otherwise - */ -static int -qconv_blind_sign_pub (void *cls, - const void *data, - size_t data_len, - void *param_values[], - int param_lengths[], - int param_formats[], - unsigned int param_length, - void *scratch[], - unsigned int scratch_length) -{ - const struct GNUNET_CRYPTO_BlindSignPublicKey *public_key = data; - size_t tlen; - size_t len; - uint32_t be; - char *buf; - void *tbuf; - - (void) cls; - (void) data_len; - GNUNET_assert (1 == param_length); - GNUNET_assert (scratch_length > 0); - GNUNET_break (NULL == cls); - be = htonl ((uint32_t) public_key->cipher); - switch (public_key->cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - tlen = GNUNET_CRYPTO_rsa_public_key_encode ( - public_key->details.rsa_public_key, - &tbuf); - break; - case GNUNET_CRYPTO_BSA_CS: - tlen = sizeof (public_key->details.cs_public_key); - break; - default: - GNUNET_assert (0); - } - len = tlen + sizeof (be); - buf = GNUNET_malloc (len); - GNUNET_memcpy (buf, - &be, - sizeof (be)); - switch (public_key->cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - GNUNET_memcpy (&buf[sizeof (be)], - tbuf, - tlen); - GNUNET_free (tbuf); - break; - case GNUNET_CRYPTO_BSA_CS: - GNUNET_memcpy (&buf[sizeof (be)], - &public_key->details.cs_public_key, - tlen); - break; - default: - GNUNET_assert (0); - } - - scratch[0] = buf; - param_values[0] = (void *) buf; - param_lengths[0] = len; - param_formats[0] = 1; - return 1; -} - - -/** - * Generate query parameter for a blind sign public key of variable size. - * - * @param public_key pointer to the query parameter to pass - */ -struct GNUNET_PQ_QueryParam -TALER_PQ_query_param_blind_sign_pub ( - const struct GNUNET_CRYPTO_BlindSignPublicKey *public_key) -{ - struct GNUNET_PQ_QueryParam res = { - .conv = &qconv_blind_sign_pub, - .data = public_key, - .num_params = 1 - }; - - return res; -} - - -/** - * Function called to convert input argument into SQL parameters. - * - * @param cls closure - * @param data pointer to input argument - * @param data_len number of bytes in @a data (if applicable) - * @param[out] param_values SQL data to set - * @param[out] param_lengths SQL length data to set - * @param[out] param_formats SQL format data to set - * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays - * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() - * @param scratch_length number of entries left in @a scratch - * @return -1 on error, number of offsets used in @a scratch otherwise - */ -static int -qconv_blind_sign_priv (void *cls, - const void *data, - size_t data_len, - void *param_values[], - int param_lengths[], - int param_formats[], - unsigned int param_length, - void *scratch[], - unsigned int scratch_length) -{ - const struct GNUNET_CRYPTO_BlindSignPrivateKey *private_key = data; - size_t tlen; - size_t len; - uint32_t be; - char *buf; - void *tbuf; - - (void) cls; - (void) data_len; - GNUNET_assert (1 == param_length); - GNUNET_assert (scratch_length > 0); - GNUNET_break (NULL == cls); - be = htonl ((uint32_t) private_key->cipher); - switch (private_key->cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - tlen = GNUNET_CRYPTO_rsa_private_key_encode ( - private_key->details.rsa_private_key, - &tbuf); - break; - case GNUNET_CRYPTO_BSA_CS: - tlen = sizeof (private_key->details.cs_private_key); - break; - default: - GNUNET_assert (0); - } - len = tlen + sizeof (be); - buf = GNUNET_malloc (len); - GNUNET_memcpy (buf, - &be, - sizeof (be)); - switch (private_key->cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - GNUNET_memcpy (&buf[sizeof (be)], - tbuf, - tlen); - GNUNET_free (tbuf); - break; - case GNUNET_CRYPTO_BSA_CS: - GNUNET_memcpy (&buf[sizeof (be)], - &private_key->details.cs_private_key, - tlen); - break; - default: - GNUNET_assert (0); - } - - scratch[0] = buf; - param_values[0] = (void *) buf; - param_lengths[0] = len; - param_formats[0] = 1; - return 1; -} - - -/** - * Generate query parameter for a blind sign private key of variable size. - * - * @param private_key pointer to the query parameter to pass - */ -struct GNUNET_PQ_QueryParam -TALER_PQ_query_param_blind_sign_priv ( - const struct GNUNET_CRYPTO_BlindSignPrivateKey *private_key) -{ - struct GNUNET_PQ_QueryParam res = { - .conv = &qconv_blind_sign_priv, - .data = private_key, - .num_params = 1 - }; - - return res; -} - - /* end of pq/pq_query_helper.c */ diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c index e81c78302..384200cfb 100644 --- a/src/pq/pq_result_helper.c +++ b/src/pq/pq_result_helper.c @@ -1544,8 +1544,6 @@ TALER_PQ_result_spec_array_amount ( .cls = info, }; return res; - - } @@ -1574,9 +1572,6 @@ TALER_PQ_result_spec_array_hash_code ( .cls = info, }; return res; - - } - /* end of pq_result_helper.c */ diff --git a/src/templating/.gitignore b/src/templating/.gitignore index ac9c37f45..9ed2f3ff9 100644 --- a/src/templating/.gitignore +++ b/src/templating/.gitignore @@ -1,2 +1,3 @@ test_mustach_jansson taler-mustach-tool +mustach diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am index c55f2964d..a79b109d1 100644 --- a/src/templating/Makefile.am +++ b/src/templating/Makefile.am @@ -62,10 +62,71 @@ test_mustach_jansson_LDADD = \ check_PROGRAMS = \ test_mustach_jansson -check_SCRIPTS = \ - run-original-tests.sh - -TESTS = $(check_SCRIPTS) $(check_PROGRAMS) +TESTS = $(check_PROGRAMS) EXTRA_DIST = \ - $(check_SCRIPTS) + $(check_SCRIPTS) \ + mustach-original-Makefile \ + mustach.1.gz \ + mustach.1.scd \ + meson.build \ + LICENSE.txt \ + ORIGIN \ + pkgcfgs \ + README.md \ + dotest.sh \ + AUTHORS \ + CHANGELOG.md \ + mustach-json-c.h \ + mustach-json-c.c \ + mustach-cjson.h \ + mustach-cjson.c \ + test1/json \ + test1/Makefile \ + test1/must \ + test1/resu.ref \ + test1/vg.ref \ + test2/json \ + test2/Makefile \ + test2/must \ + test2/resu.ref \ + test2/vg.ref \ + test3/json \ + test3/Makefile \ + test3/must \ + test3/resu.ref \ + test3/vg.ref \ + test4/json \ + test4/Makefile \ + test4/must \ + test4/resu.ref \ + test4/vg.ref \ + test5/json \ + test5/Makefile \ + test5/must \ + test5/must2 \ + test5/must2.mustache \ + test5/must3.mustache \ + test5/resu.ref \ + test5/vg.ref \ + test6/json \ + test6/Makefile \ + test6/must \ + test6/resu.ref \ + test6/test-custom-write.c \ + test6/vg.ref \ + test7/base.mustache \ + test7/json \ + test7/Makefile \ + test7/node.mustache \ + test7/resu.ref \ + test7/vg.ref \ + test8/json \ + test8/Makefile \ + test8/must \ + test8/resu.ref \ + test8/vg.ref \ + test-specs/test-specs.c \ + test-specs/test-specs-cjson.ref \ + test-specs/test-specs-jansson.ref \ + test-specs/test-specs-json-c.ref diff --git a/src/templating/dotest.sh b/src/templating/dotest.sh index 99ff8b201..32f575c2e 100755 --- a/src/templating/dotest.sh +++ b/src/templating/dotest.sh @@ -1,20 +1,26 @@ #!/bin/sh -mustach=${mustach:-../mustach} -echo starting test -if test "$NOVALGRIND" = 1 +# Exit, with error message (hard failure) +exit_fail() { + echo " FAIL: " "$@" >&2 + exit 1 +} + +mustach="${mustach:-../mustach}" +echo "starting test" +if ! valgrind --version 2> /dev/null then - $mustach "$@" > resu.last + $mustach "$@" > resu.last || exit_fail "ERROR! mustach command failed ($?)!" else - valgrind $mustach "$@" > resu.last 2> vg.last + valgrind $mustach "$@" > resu.last 2> vg.last || exit_fail "ERROR! valgrind + mustach command failed ($?)!" sed -i 's:^==[0-9]*== ::' vg.last - awk '/^ *total heap usage: .* allocs, .* frees,.*/{if($$4-$$6)exit(1)}' vg.last || echo "ERROR! Alloc/Free issue" + awk '/^ *total heap usage: .* allocs, .* frees,.*/{if($$4-$$6)exit(1)}' vg.last || exit_fail "ERROR! Alloc/Free issue" fi if diff -w resu.ref resu.last then echo "result ok" else - echo "ERROR! Result differs" + exit_fail "ERROR! Result differs" fi echo - +exit 0 diff --git a/src/templating/mustach-original-Makefile b/src/templating/mustach-original-Makefile index c1ddbe63f..aee827583 100644 --- a/src/templating/mustach-original-Makefile +++ b/src/templating/mustach-original-Makefile @@ -237,14 +237,6 @@ uninstall: rm -f $(DESTDIR)$(LIBDIR)/libmustach*.so* rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach -# testing -ifeq ($(valgrind),no) - NOVALGRIND := 1 -else - NOVALGRIND := $(shell which -s valgrind && echo 0 || echo 1) -endif -export NOVALGRIND - .PHONY: test test-basic test-specs test: basic-tests spec-tests @@ -311,4 +303,3 @@ manuals: mustach.1.gz mustach.1.gz: mustach.1.scd if which scdoc >/dev/null 2>&1; then scdoc < mustach.1.scd | gzip > mustach.1.gz; fi - diff --git a/src/templating/run-original-tests.sh b/src/templating/run-original-tests.sh index cc666fd69..21481a286 100755 --- a/src/templating/run-original-tests.sh +++ b/src/templating/run-original-tests.sh @@ -1,18 +1,10 @@ #!/bin/bash # This file is in the public domain. -set -eu +set -eux export CFLAGS="-g" -function build() -{ - make clean - make -} - -# Install rebuild-on-exit handler (except for kill -9) -trap build EXIT - +echo "Ensuring clean state on entry to upstream tests ..." make clean # The build fails if libjson-c-dev is not installed. @@ -23,3 +15,5 @@ make -f mustach-original-Makefile mustach mustach-json-c.o || exit 77 make -f mustach-original-Makefile clean || true make -f mustach-original-Makefile basic-tests make -f mustach-original-Makefile clean || true + +exit 0 diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c index 3bda7de55..88a17c682 100644 --- a/src/templating/templating_api.c +++ b/src/templating/templating_api.c @@ -221,7 +221,7 @@ TALER_TEMPLATING_build (struct MHD_Connection *connection, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load template `%s'\n", template); - *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + *http_status = MHD_HTTP_NOT_ACCEPTABLE; *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE, template); return GNUNET_NO; @@ -432,11 +432,12 @@ load_template (void *cls, MHD_RESULT -TALER_TEMPLATING_reply_error (struct MHD_Connection *connection, - const char *template_basename, - unsigned int http_status, - enum TALER_ErrorCode ec, - const char *detail) +TALER_TEMPLATING_reply_error ( + struct MHD_Connection *connection, + const char *template_basename, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *detail) { json_t *data; enum GNUNET_GenericReturnValue ret; diff --git a/src/templating/test7/Makefile b/src/templating/test7/Makefile new file mode 100644 index 000000000..8e3a3b990 --- /dev/null +++ b/src/templating/test7/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json base.mustache + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test8/Makefile b/src/templating/test8/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test8/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/testing/taler-unified-setup.sh b/src/testing/taler-unified-setup.sh index 2be6a97f1..d933cc819 100755 --- a/src/testing/taler-unified-setup.sh +++ b/src/testing/taler-unified-setup.sh @@ -29,6 +29,11 @@ set -eu +# These break TALER_HOME control via TALER_TEST_HOME... +unset XDG_DATA_HOME +unset XDG_CONFIG_HOME +unset XDG_CACHE_HOME + EXIT_STATUS=2 # Exit, with status code "skip" (no 'real' failure) diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 2aea6b689..d2504588b 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -120,7 +120,7 @@ libtalerutil_la_LIBADD = \ -lm libtalerutil_la_LDFLAGS = \ - -version-info 3:1:2 \ + -version-info 3:3:2 \ -no-undefined diff --git a/src/util/crypto_confirmation.c b/src/util/crypto_confirmation.c index 204c373da..99552f150 100644 --- a/src/util/crypto_confirmation.c +++ b/src/util/crypto_confirmation.c @@ -261,6 +261,13 @@ TALER_build_pos_confirmation (const char *pos_key, struct GNUNET_HashCode hkey; struct TALER_AmountNBO ntotal; + if ( (NULL == total) || + (GNUNET_YES != + TALER_amount_is_valid (total) ) ) + { + GNUNET_break_op (0); + return NULL; + } TALER_amount_hton (&ntotal, total); GNUNET_assert (GNUNET_YES == diff --git a/src/util/payto.c b/src/util/payto.c index 5e0391883..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-2022 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 @@ -101,7 +101,9 @@ TALER_payto_get_method (const char *payto_uri) 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, @@ -111,23 +113,27 @@ TALER_xtalerbank_account_from_payto (const char *payto) 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) + end = &beg[strlen (beg)]; + while ( (NULL != nxt) && + (end - nxt > 0) ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid payto URI `%s'\n", - payto); - GNUNET_break_op (0); - return GNUNET_strdup (beg); /* optional part is missing */ + beg = nxt + 1; + nxt = strchr (beg, + '/'); } return GNUNET_strndup (beg, end - beg); @@ -200,36 +206,63 @@ 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/" +#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 - user = strchr (host, '/'); - if (NULL == user) + beg = strchr (host, + '/'); + if (NULL == beg) { return GNUNET_strdup ("account name missing"); } - if (user == host) + 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"); - if (NULL != strchr (user + 1, '/')) - return GNUNET_strdup ("invalid character '/' after account 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; @@ -247,6 +280,7 @@ validate_payto_xtalerbank (const char *account_url) c); return err; } + port_ok = true; } else { @@ -275,6 +309,10 @@ validate_payto_xtalerbank (const char *account_url) } host++; } + if (post_colon && (! port_ok) ) + { + return GNUNET_strdup ("port missing after ':'"); + } { char *target; @@ -304,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])) { @@ -358,10 +396,10 @@ TALER_payto_get_receiver_name (const char *payto) /** - * Normalize "payto://x-taler-bank/$HOSTNAME/$USERNAME" + * Normalize "payto://x-taler-bank/$HOSTNAME/[$PATH/]$USERNAME" * URI in @a input. * - * Converts to lower-case, except for $USERNAME which + * Converts to lower-case, except for [$PATH/]$USERNAME which * is case-sensitive. * * @param len number of bytes in @a input diff --git a/src/util/test_payto.c b/src/util/test_payto.c index b37a6f721..62ba7d28e 100644 --- a/src/util/test_payto.c +++ b/src/util/test_payto.c @@ -22,16 +22,16 @@ #include "taler_util.h" #define CHECK(a,b) do { \ - GNUNET_assert (a != NULL); \ - GNUNET_assert (b != NULL); \ - if (0 != strcmp (a,b)) { \ - GNUNET_break (0); \ - fprintf (stderr, "Got %s, wanted %s\n", b, a); \ - GNUNET_free (b); \ - return 1; \ - } else { \ - GNUNET_free (b); \ - } \ + GNUNET_assert (a != NULL); \ + GNUNET_assert (b != NULL); \ + if (0 != strcmp (a,b)) { \ + GNUNET_break (0); \ + fprintf (stderr, "Got %s, wanted %s\n", b, a); \ + GNUNET_free (b); \ + return 1; \ + } else { \ + GNUNET_free (b); \ + } \ } while (0) @@ -54,11 +54,20 @@ main (int argc, "payto://x-taler-bank/hostname/username?receiver-name=foo"); GNUNET_assert (NULL == r); r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/~path/username?receiver-name=foo"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/~path/username?receiver-name=fo/o"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/path/username?receiver-name=foo"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( "payto://x-taler-bank/https://hostname/username?receiver-name=foo"); GNUNET_assert (NULL != r); GNUNET_free (r); r = TALER_payto_validate ( - "payto://x-taler-bank/hostname/username/extra?receiver-name=foo"); + "payto://x-taler-bank/hostname:4a2/path/username?receiver-name=foo"); GNUNET_assert (NULL != r); GNUNET_free (r); r = TALER_payto_validate ( @@ -82,6 +91,14 @@ main (int argc, CHECK ("alice", r); r = TALER_xtalerbank_account_from_payto ( + "payto://x-taler-bank/localhost:1080/path/alice"); + CHECK ("alice", + r); + r = TALER_xtalerbank_account_from_payto ( + "payto://x-taler-bank/localhost:1080/path/alice?receiver-name=ali/cia"); + CHECK ("alice", + r); + r = TALER_xtalerbank_account_from_payto ( "payto://x-taler-bank/localhost:1080/alice?subject=hello&amount=EUR:1"); CHECK ("alice", r); |