exchange

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

commit 13e058a902a3dbee9d7fe327030b88c2d126675b
parent 5e715eabb211fc9310ae7ccf0419267699a3f5dc
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  2 Mar 2025 00:53:00 +0100

update to match latest GNUnet APIs, begin work on libtalermhd2

Diffstat:
MREADME | 2+-
Mconfigure.ac | 25+++++++++++++------------
Mcontrib/microhttpd.tag | 18+++++++++++++++++-
Msrc/bank-lib/Makefile.am | 1+
Msrc/bank-lib/fakebank_bank_post_accounts_token.c | 27++++++++++++++-------------
Msrc/bank-lib/fakebank_bank_post_accounts_withdrawals.c | 28+++++++++++++++-------------
Msrc/bank-lib/fakebank_bank_post_withdrawals_id_op.c | 27++++++++++++++-------------
Msrc/bank-lib/fakebank_bank_testing_register.c | 27++++++++++++++-------------
Msrc/bank-lib/fakebank_tbi_post_withdrawal_operation.c | 27++++++++++++++-------------
Msrc/bank-lib/fakebank_twg_admin_add_incoming.c | 27++++++++++++++-------------
Msrc/bank-lib/fakebank_twg_admin_add_kycauth.c | 27++++++++++++++-------------
Msrc/bank-lib/fakebank_twg_transfer.c | 27++++++++++++++-------------
Msrc/bank-lib/taler-exchange-wire-gateway-client.c | 1+
Msrc/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 8++++----
Msrc/include/Makefile.am | 5+++++
Msrc/include/taler_kyclogic_plugin.h | 1+
Asrc/include/taler_mhd2_lib.h | 1037+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler_mhd_lib.h | 74+++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/include/taler_util.h | 51---------------------------------------------------
Msrc/mhd/Makefile.am | 9++++-----
Asrc/mhd/mhd.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/mhd2_responses.c | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/mhd2_run.c | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd/mhd_parsing.c | 29+++++++++++++++--------------
Msrc/mhd/mhd_responses.c | 81-------------------------------------------------------------------------------
Msrc/util/Makefile.am | 1-
Dsrc/util/mhd.c | 135-------------------------------------------------------------------------------
Msrc/util/yna.c | 44--------------------------------------------
28 files changed, 2111 insertions(+), 486 deletions(-)

diff --git a/README b/README @@ -60,7 +60,7 @@ Direct dependencies These are the direct dependencies for running a Taler exchange: -- GNUnet >= 0.23.0 +- GNUnet >= 0.23.3 - GNU libmicrohttpd >= 0.9.71 - PostgreSQL >= 15.0 diff --git a/configure.ac b/configure.ac @@ -143,7 +143,7 @@ AS_CASE([$with_microhttpd], MHD_VERSION_AT_LEAST([0.9.71]) -# check for libmicrohttpd +# check for libmicrohttpd2 AC_MSG_CHECKING([for microhttpd2]) AC_ARG_WITH([microhttpd2], [AS_HELP_STRING([--with-microhttpd2=PFX], [base of libmicrohttpd2 installation])], @@ -151,8 +151,8 @@ AC_ARG_WITH([microhttpd2], [AC_MSG_RESULT([not given]) with_microhttpd2=yes]) AS_CASE([$with_microhttpd2], - [yes],, - [no],, + [yes],[], + [no],[], [LDFLAGS="-L$with_microhttpd2/lib $LDFLAGS" CPPFLAGS="-I$with_microhttpd2/include $CPPFLAGS"]) MHD2_VERSION_AT_LEAST([1.99.0]) @@ -198,15 +198,15 @@ AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h], AS_IF([test $libgnunetutil != 1], [AC_MSG_ERROR([[ *** -*** You need libgnunetutil >= 0.21.1 to build this program. +*** You need libgnunetutil >= 0.23.3 to build this program. *** This library is part of GNUnet, available at *** https://gnunet.org *** ]])]) -# Check for GNUnet's libgnunetjson. +# Check for GNUnet's libgnunetmhd. libgnunetjson=0 -AC_MSG_CHECKING([for libgnunetjson]) +AC_MSG_CHECKING([for libgnunetmhd]) AC_ARG_WITH(gnunet, [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet installation])], [AC_MSG_RESULT([given as $with_gnunet])], @@ -217,14 +217,15 @@ AS_CASE([$with_gnunet], [no], [AC_MSG_ERROR([--with-gnunet is required])], [LDFLAGS="-L$with_gnunet/lib $LDFLAGS" CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"]) -AC_CHECK_HEADERS([gnunet/gnunet_json_lib.h], - [AC_CHECK_LIB([gnunetjson], [GNUNET_JSON_parse], libgnunetjson=1)]) -AS_IF([test $libgnunetjson != 1], +AC_CHECK_HEADERS([gnunet/gnunet_mhd_lib.h], + [AC_CHECK_LIB([gnunetmhd], [GNUNET_MHD_parse], libgnunetmhd=1)]) +AS_IF([test $libgnunetmhd != 1], [AC_MSG_ERROR([[ *** -*** You need libgnunetjson to build this program. -*** Make sure you have libjansson installed while -*** building GNUnet. +*** You need GNUnet >= 0.23.3 to build this program. +*** You need libgnunetmhd to build this program. +*** Make sure you have libmicrohttpd and libjansson +*** installed while building GNUnet. *** ]])]) # check for gettext diff --git a/contrib/microhttpd.tag b/contrib/microhttpd.tag @@ -1,7 +1,23 @@ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <tagfile> <compound kind="file"> - <name>microhttpd_lib.h</name> + <name>microhttpd2.h</name> + <path></path> + <member kind="define"> + <type>#define</type> + <name>MHD_FD_STATE_NONE</name> + <anchorfile>microhttpd2.h</anchorfile> + <arglist></arglist> + </member> + <member kind="function"> + <type>#define</type> + <name>MHD_daemon_event_update</name> + <anchorfile>microhttpd2.h</anchorfile> + <arglist></arglist> + </member> + </compound> + <compound kind="file"> + <name>microhttpd.h</name> <path></path> <filename>microhttpd.h</filename> <member kind="define"> diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am @@ -51,6 +51,7 @@ libtalerbank_la_LIBADD = \ $(top_builddir)/src/util/libtalerutil.la \ -lgnunetcurl \ -lgnunetjson \ + -lgnunetmhd \ -lgnunetutil \ -ljansson \ -lcurl \ diff --git a/src/bank-lib/fakebank_bank_post_accounts_token.c b/src/bank-lib/fakebank_bank_post_accounts_token.c @@ -27,6 +27,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_bank_post_accounts_token.h" #include "fakebank_common_lookup.h" @@ -118,36 +119,36 @@ TALER_FAKEBANK_bank_post_accounts_token_ ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; MHD_RESULT res; if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c @@ -27,6 +27,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_bank_post_accounts_withdrawals.h" #include "fakebank_common_lookup.h" @@ -89,6 +90,7 @@ do_post_account_withdrawals ( GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + break; } { @@ -140,36 +142,36 @@ TALER_FAKEBANK_bank_post_account_withdrawals_ ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; MHD_RESULT res; if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } diff --git a/src/bank-lib/fakebank_bank_post_withdrawals_id_op.c b/src/bank-lib/fakebank_bank_post_withdrawals_id_op.c @@ -27,6 +27,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_bank_post_withdrawals_id_op.h" #include "fakebank_common_lookup.h" @@ -276,33 +277,33 @@ TALER_FAKEBANK_bank_withdrawals_id_op_ ( if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } if (0 != *upload_data_size) { - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } } diff --git a/src/bank-lib/fakebank_bank_testing_register.c b/src/bank-lib/fakebank_bank_testing_register.c @@ -26,6 +26,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_bank_testing_register.h" @@ -39,36 +40,36 @@ TALER_FAKEBANK_bank_testing_register_ ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; MHD_RESULT res; if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c @@ -27,6 +27,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_common_lookup.h" #include "fakebank_tbi_post_withdrawal_operation.h" @@ -205,36 +206,36 @@ TALER_FAKEBANK_tbi_post_withdrawal ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; MHD_RESULT res; if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.c b/src/bank-lib/fakebank_twg_admin_add_incoming.c @@ -26,6 +26,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_common_make_admin_transfer.h" #include "fakebank_twg_admin_add_incoming.h" @@ -40,7 +41,7 @@ TALER_FAKEBANK_twg_admin_add_incoming_ ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; uint64_t row_id; struct GNUNET_TIME_Timestamp timestamp; @@ -48,29 +49,29 @@ TALER_FAKEBANK_twg_admin_add_incoming_ ( if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } { diff --git a/src/bank-lib/fakebank_twg_admin_add_kycauth.c b/src/bank-lib/fakebank_twg_admin_add_kycauth.c @@ -26,6 +26,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_common_make_admin_transfer.h" #include "fakebank_twg_admin_add_kycauth.h" @@ -41,7 +42,7 @@ TALER_FAKEBANK_twg_admin_add_kycauth_ ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; uint64_t row_id; struct GNUNET_TIME_Timestamp timestamp; @@ -49,29 +50,29 @@ TALER_FAKEBANK_twg_admin_add_kycauth_ ( if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } { diff --git a/src/bank-lib/fakebank_twg_transfer.c b/src/bank-lib/fakebank_twg_transfer.c @@ -26,6 +26,7 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include <gnunet/gnunet_mhd_compat.h> +#include <gnunet/gnunet_mhd_lib.h> #include "fakebank.h" #include "fakebank_common_transact.h" #include "fakebank_twg_transfer.h" @@ -52,7 +53,7 @@ TALER_FAKEBANK_handle_transfer_ ( void **con_cls) { struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; + enum GNUNET_MHD_PostResult pr; json_t *json; uint64_t row_id; struct GNUNET_TIME_Timestamp ts; @@ -60,29 +61,29 @@ TALER_FAKEBANK_handle_transfer_ ( if (NULL == cc) { cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + cc->ctx_cleaner = &GNUNET_MHD_post_parser_cleanup; *con_cls = cc; } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - upload_data, - upload_data_size, - &json); + pr = GNUNET_MHD_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + upload_data, + upload_data_size, + &json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: return MHD_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (0); return MHD_NO; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: break; } { diff --git a/src/bank-lib/taler-exchange-wire-gateway-client.c b/src/bank-lib/taler-exchange-wire-gateway-client.c @@ -22,6 +22,7 @@ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_json_lib.h> #include <jansson.h> +#include <microhttpd.h> #include "taler_bank_service.h" /** diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -349,11 +349,11 @@ calculate_blinded_hash ( if (GNUNET_OK != ret) { GNUNET_break (0); - *result = TALER_MHD_reply_json_pack (connection, + *result = TALER_MHD_REPLY_JSON_PACK (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "{ss}", - "details", - "failed to prepare planchet from base key"); + GNUNET_JSON_pack_string ( + "details", + "failed to prepare planchet from base key")); return ret; } diff --git a/src/include/Makefile.am b/src/include/Makefile.am @@ -31,6 +31,11 @@ talerinclude_HEADERS = \ taler_templating_lib.h \ taler_twister_testing_lib.h +if HAVE_MHD2 +talerinclude_HEADERS += \ + taler_mhd2_lib.h +endif + EXTRA_DIST = \ backoff.h \ gauger.h diff --git a/src/include/taler_kyclogic_plugin.h b/src/include/taler_kyclogic_plugin.h @@ -22,6 +22,7 @@ #define TALER_KYCLOGIC_PLUGIN_H #include <jansson.h> +#include <microhttpd.h> #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_db_lib.h> #include "taler_util.h" diff --git a/src/include/taler_mhd2_lib.h b/src/include/taler_mhd2_lib.h @@ -0,0 +1,1037 @@ +/* + This file is part of TALER + Copyright (C) 2014-2025 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler_mhd2_lib.h + * @brief API for generating MHD replies + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MHD2_LIB_H +#define TALER_MHD2_LIB_H +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd2.h> +#include "taler_error_codes.h" +#include "taler_util.h" + + +/** + * Maximum POST request size. + */ +#define TALER_MHD2_REQUEST_BUFFER_MAX (1024 * 1024 * 16) + + +/** + * Global options for response generation. + */ +enum TALER_MHD2_GlobalOptions +{ + + /** + * Use defaults. + */ + TALER_MHD2_GO_NONE = 0, + + /** + * Add "Connection: Close" header. + */ + TALER_MHD2_GO_FORCE_CONNECTION_CLOSE = 1, + + /** + * Disable use of compression, even if the client + * supports it. + */ + TALER_MHD2_GO_DISABLE_COMPRESSION = 2 + +}; + + +/** + * Set global options for response generation within libtalermhd. + * + * @param go global options to use + */ +void +TALER_MHD2_setup (enum TALER_MHD2_GlobalOptions go); + + +/** + * Add headers we want to return in every response. Useful for testing, like + * if we want to always close connections. + * + * @param response response to modify + */ +void +TALER_MHD2_add_global_headers (struct MHD_Response *response); + + +/** + * Try to compress a response body. Updates @a buf and @a buf_size. + * + * @param[in,out] buf pointer to body to compress + * @param[in,out] buf_size pointer to initial size of @a buf + * @return true if @a buf was compressed + */ +bool +TALER_MHD2_body_compress (void **buf, + size_t *buf_size); + + +/** + * Is HTTP body deflate compression supported by the client? + * + * @param request the request to check + * @return true if 'deflate' compression is allowed + */ +bool +TALER_MHD2_can_compress (struct MHD_Request *request); + + +/** + * Send JSON object as response. + * + * @param request the MHD request to handle, used to determine + * whether compression is possible + * @param json the json object + * @param sc the http response code + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_json (struct MHD_Request *request, + const json_t *json, + enum MHD_HTTP_StatusCode sc); + + +/** + * Send JSON object as response, and free the @a json + * object. + * + * @param request the MHD request to handle + * @param json the json object (freed!) + * @param sc the http response code + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_json_steal (struct MHD_Request *request, + json_t *json, + enum MHD_HTTP_StatusCode sc); + + +/** + * Make JSON response object. + * + * @param sc HTTP response code to use + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TALER_MHD2_make_json (enum MHD_HTTP_StatusCode sc, + const json_t *json); + + +/** + * Make JSON response object and free @a json. + * + * @param sc HTTP response code to use + * @param json the json object, freed. + * @return MHD response object + */ +struct MHD_Response * +TALER_MHD2_make_json_steal (enum MHD_HTTP_StatusCode sc, + json_t *json); + + +/** + * Make response object + * + * @param sc HTTP status code to use + * @param ... varargs + * @return MHD action + */ +#define TALER_MHD2_MAKE_JSON_PACK(sc,...) \ + TALER_MHD2_make_json_steal (sc, GNUNET_JSON_PACK (__VA_ARGS__)) + + +/** + * Pack Taler error code @a ec and associated hint into a + * JSON object. + * + * @param ec error code to pack + * @return packer array entries (two!) + */ +#define TALER_MHD2_PACK_EC(ec) \ + GNUNET_JSON_pack_uint64 ("code", ec), \ + GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)) + +/** + * Create a response indicating an internal error. + * + * @param ec error code to return + * @param detail additional optional detail about the error, can be NULL + * @return a MHD response object + */ +struct MHD_Response * +TALER_MHD2_make_error (enum TALER_ErrorCode ec, + const char *detail); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param request the MHD request to handle + * @param sc HTTP response code to use + * @param ... varargs of JSON pack specification + * @return MHD action + */ +#define TALER_MHD2_REPLY_JSON_PACK(request,sc,...) \ + TALER_MHD2_reply_json_steal (request, GNUNET_JSON_PACK (__VA_ARGS__), \ + sc) + + +/** + * Send a response indicating an error. + * + * @param request the MHD request to use + * @param ec error code uniquely identifying the error + * @param sc HTTP status code to use + * @param detail additional optional detail about the error + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_with_error (struct MHD_Request *request, + enum MHD_HTTP_StatusCode sc, + enum TALER_ErrorCode ec, + const char *detail); + + +/** + * Send a response indicating an error. The HTTP status code is + * to be derived from the @a ec. + * + * @param request the MHD request to use + * @param ec error code uniquely identifying the error + * @param detail additional optional detail about the error + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_with_ec (struct MHD_Request *request, + enum TALER_ErrorCode ec, + const char *detail); + + +/** + * Produce HTTP "Date:" header. + * + * @param at time to write to @a date + * @param[out] date where to write the header, with + * at least 128 bytes available space. + */ +void +TALER_MHD2_get_date_string (struct GNUNET_TIME_Absolute at, + char date[128]); + + +/** + * Send a response indicating that the request was too big. + * + * @param request the MHD request to use + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_request_too_large (struct MHD_Request *request); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param request the MHD request to handle + * @param url where to redirect for the sources + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_agpl (struct MHD_Request *request, + const char *url); + + +/** + * Function to call to handle the request by sending + * back static data. + * + * @param request the MHD request to handle + * @param sc status code to return + * @param mime_type content-type to use + * @param body response payload + * @param body_size number of bytes in @a body + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_static (struct MHD_Request *request, + enum MHD_HTTP_StatusCode sc, + const char *mime_type, + const char *body, + size_t body_size); + + +#if FIXME + +/** + * Process a POST request containing a JSON object. This + * function realizes an MHD POST processor that will + * (incrementally) process JSON data uploaded to the HTTP + * server. It will store the required state in the + * "request_cls", which must be cleaned up using + * #TALER_MHD2_parse_post_cleanup_callback(). + * + * @param request the MHD request + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + * #GNUNET_YES if json object was parsed or at least + * may be parsed in the future (call again); + * `*json` will be NULL if we need to be called again, + * and non-NULL if we are done. + * #GNUNET_NO is request incomplete or invalid + * (error message was generated) + * #GNUNET_SYSERR on internal error + * (we could not even queue an error message, + * close HTTP session with MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_post_json (struct MHD_Request *request, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json); + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TALER_MHD2_parse_post_json(), to be cleaned up + */ +void +TALER_MHD2_parse_post_cleanup_callback (void *con_cls); + + +/** + * Parse JSON object into components based on the given field + * specification. If parsing fails, we return an HTTP + * status code of 400 (#MHD_HTTP_BAD_REQUEST). + * + * @param request the request to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_json_data (struct MHD_Request *request, + const json_t *root, + struct GNUNET_JSON_Specification *spec); + + +/** + * Parse JSON object that we (the server!) generated into components based on + * the given field specification. The difference to + * #TALER_MHD2_parse_json_data() is that this function will fail + * with an HTTP failure of 500 (internal server error) in case + * parsing fails, instead of blaming it on the client with a + * 400 (#MHD_HTTP_BAD_REQUEST). + * + * @param request the request to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_internal_json_data (struct MHD_Request *request, + const json_t *root, + struct GNUNET_JSON_Specification *spec); + + +/** + * Parse JSON array into components based on the given field + * specification. Generates error response on parse errors. + * + * @param request the request to send an error response to + * @param root the JSON node to start the navigation at. + * @param[in,out] spec field specification for the parser + * @param ... -1-terminated list of array offsets of type 'int' + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_json_array (struct MHD_Request *request, + const json_t *root, + struct GNUNET_JSON_Specification *spec, + ...); + + +/** + * Extract optional "timeout_ms" argument from request. + * + * @param request the MHD request + * @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout, + * the current time plus the value given under "timeout_ms" otherwise + * @return #GNUNET_OK on success, #GNUNET_NO if an + * error was returned on @a request (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_arg_timeout (struct MHD_Request *request, + struct GNUNET_TIME_Absolute *expiration); + + +/** + * Extract optional "timeout_ms" argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the "timeout_ms" + * argument existed but failed to parse. + * + * @param request the MHD request + * @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout, + * the current time plus the value given under "timeout_ms" otherwise + */ +#define TALER_MHD2_parse_request_timeout(request,expiration) \ + do { \ + switch (TALER_MHD2_parse_request_arg_timeout (request, \ + expiration)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Extract optional timestamp argument from request. + * + * @param request the MHD request + * @param fname name of the argument to parse + * @param[out] ts set to #GNUNET_TIME_UNIT_ZERO_TS if there was no timestamp + * @return #GNUNET_OK on success, #GNUNET_NO if an + * error was returned on @a request (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_arg_timestamp (struct MHD_Request *request, + const char *fname, + struct GNUNET_TIME_Timestamp *ts); + + +/** + * Extract optional timestamp argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the timestamp + * argument existed but failed to parse. + * + * @param request the MHD request + * @param fname name of the argument + * @param[out] ts set to #GNUNET_TIME_UNIT_ZERO_TS if there was no timestamp + */ +#define TALER_MHD2_parse_request_timestamp(request,fname,ts) \ + do { \ + switch (TALER_MHD2_parse_request_arg_timestamp (request, \ + fname, \ + ts)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Extract optional "yes/no/all" argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the + * argument existed but failed to parse. + * + * @param request the MHD request + * @param name name of the query parameter to parse + * @param def default value to set if absent + * @param[out] ret set to the yes/no/all value + */ +#define TALER_MHD2_parse_request_yna(request,name,def,ret) \ + do { \ + if (! (TALER_arg_to_yna (request, \ + name, \ + def, \ + ret)) ) \ + { \ + GNUNET_break_op (0); \ + return TALER_MHD2_reply_with_error ( \ + request, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MALFORMED, \ + name); \ + } \ + } while (0) + +/** + * Extract optional numeric limit argument from request. + * + * @param request the MHD request + * @param name name of the query parameter + * @param[out] off set to the offset, unchanged if the + * option was not given + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error was returned on @a request (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_arg_number (struct MHD_Request *request, + const char *name, + uint64_t *off); + + +/** + * Extract optional numeric argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the + * requested argument existed but failed to parse. + * + * @param request the MHD request + * @param name name of the argument to parse + * @param[out] off set to the given numeric value, + * unchanged if value was not specified + */ +#define TALER_MHD2_parse_request_number(request,name,off) \ + do { \ + switch (TALER_MHD2_parse_request_arg_number (request, \ + name, \ + off)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Extract optional signed numeric limit argument from request. + * + * @param request the MHD request + * @param name name of the query parameter + * @param[out] val set to the signed value, unchanged if the + * option was not given + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error was returned on @a request (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_arg_snumber (struct MHD_Request *request, + const char *name, + int64_t *val); + + +/** + * Extract optional numeric argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the + * requested argument existed but failed to parse. + * + * @param request the MHD request + * @param name name of the argument to parse + * @param[out] val set to the given numeric value, + * unchanged if value was not specified + */ +#define TALER_MHD2_parse_request_snumber(request,name,val) \ + do { \ + switch (TALER_MHD2_parse_request_arg_snumber (request, \ + name, \ + val)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Extract optional amount argument from request. + * + * @param request the MHD request + * @param name name of the query parameter + * @param[out] val set to the amount, unchanged if the + * option was not given + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error was returned on @a request (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_arg_amount (struct MHD_Request *request, + const char *name, + struct TALER_Amount *val); + + +/** + * Extract optional amount argument from request. Macro that *returns* + * #MHD_YES/#MHD_NO if the requested argument existed but failed to parse. + * + * @param request the MHD request + * @param name name of the argument to parse + * @param[out] val set to the given amount, + * unchanged if value was not specified + */ +#define TALER_MHD2_parse_request_amount(request,name,val) \ + do { \ + switch (TALER_MHD2_parse_request_arg_amount (request, \ + name, \ + val)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Extract fixed-size base32crockford encoded data from request argument. + * + * Queues an error response to the request if the parameter is missing or + * invalid. + * + * @param request the MHD request + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of @a out_data + * @param[out] present set to true if argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_arg_data (struct MHD_Request *request, + const char *param_name, + void *out_data, + size_t out_size, + bool *present); + + +/** + * Extract fixed-size base32crockford encoded data from request header. + * + * Queues an error response to the request if the parameter is missing or + * invalid. + * + * @param request the MHD request + * @param header_name the name of the HTTP header with the value + * @param[out] out_data pointer to store the result + * @param out_size expected size of @a out_data + * @param[out] present set to true if argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_request_header_data (struct MHD_Request *request, + const char *header_name, + void *out_data, + size_t out_size, + bool *present); + +/** + * Extract fixed-size base32crockford encoded data from request. + * + * @param request the MHD request + * @param name the name of the parameter with the key + * @param[out] val pointer to store the result, type must determine size + * @param[in,out] required pass true to require presence of this argument; if 'false' + * set to true if the argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD2_parse_request_arg_auto(request,name,val,required) \ + do { \ + bool p; \ + switch (TALER_MHD2_parse_request_arg_data (request, 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)) \ + { \ + GNUNET_break_op (0); \ + return TALER_MHD2_reply_with_error ( \ + request, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MISSING, \ + name); \ + } \ + required = p; \ + break; \ + } \ + } while (0) + + +/** + * Extract required fixed-size base32crockford encoded data from request. + * + * @param request the MHD request + * @param name the name of the parameter with the key + * @param[out] val pointer to store the result, type must determine size + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD2_parse_request_arg_auto_t(request,name,val) \ + do { \ + bool b = true; \ + TALER_MHD2_parse_request_arg_auto (request,name,val,b); \ + } while (0) + +/** + * Extract fixed-size base32crockford encoded data from request. + * + * @param request the MHD request + * @param name the name of the header with the key + * @param[out] val pointer to store the result, type must determine size + * @param[in,out] required pass true to require presence of this argument; if 'false' + * set to true if the argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD2_parse_request_header_auto(request,name,val,required) \ + do { \ + bool p; \ + switch (TALER_MHD2_parse_request_header_data (request, 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_MHD2_reply_with_error ( \ + request, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MISSING, \ + name); \ + required = p; \ + break; \ + } \ + } while (0) + + +/** + * Extract required fixed-size base32crockford encoded data from request. + * + * @param request the MHD request + * @param name the name of the header with the key + * @param[out] val pointer to store the result, type must determine size + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD2_parse_request_header_auto_t(request,name,val) \ + do { \ + bool b = true; \ + TALER_MHD2_parse_request_header_auto (request,name,val,b); \ + } while (0) + +#endif + + +/** + * Check that the 'Content-Length' header is giving + * a length below @a max_len. If not, return an + * appropriate error response and return the + * correct #MHD_YES/#MHD_NO value from this function. + * + * @param request the MHD request + * @param max_len maximum allowed content length + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_check_content_length_ (struct MHD_Request *request, + unsigned long long max_len); + + +/** + * Check that the 'Content-Length' header is giving + * a length below @a max_len. If not, return an + * appropriate error response and return the + * correct #MHD_YES/#MHD_NO value from this function. + * + * @param request the MHD request + * @param max_len maximum allowed content length + */ +#define TALER_MHD2_check_content_length(request,max_len) \ + do { \ + switch (TALER_MHD2_check_content_length_ (request, 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) + + +/** + * Parse the configuration to determine on which port + * or UNIX domain path we should run an HTTP service. + * + * @param cfg configuration to parse + * @param section section of the configuration to parse (usually "exchange") + * @param[out] rport set to the port number, or 0 for none + * @param[out] unix_path set to the UNIX path, or NULL for none + * @param[out] unix_mode set to the mode to be used for @a unix_path + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TALER_MHD2_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + uint16_t *rport, + char **unix_path, + mode_t *unix_mode); + + +/** + * Function called for logging by MHD. + * + * @param cls closure, NULL + * @param fm format string (`printf()`-style) + * @param ap arguments to @a fm + */ +void +TALER_MHD2_handle_logs (void *cls, + const char *fm, + va_list ap); + + +/** + * Open UNIX domain socket for listining at @a unix_path with + * permissions @a unix_mode. + * + * @param unix_path where to listen + * @param unix_mode access permissions to set + * @return -1 on error, otherwise the listen socket + */ +int +TALER_MHD2_open_unix_path (const char *unix_path, + mode_t unix_mode); + + +/** + * Bind a listen socket to the UNIX domain path or the TCP port and IP address + * as specified in @a cfg in section @a section. IF only a port was + * specified, set @a port and return -1. Otherwise, return the bound file + * descriptor. + * + * @param cfg configuration to parse + * @param section configuration section to use + * @param[out] port port to set, if TCP without BINDTO + * @return -1 and a port of zero on error, otherwise + * either -1 and a port, or a bound stream socket + */ +int +TALER_MHD2_bind (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + uint16_t *port); + + +/** + * Start to run an event loop for @a daemon. + * Only one daemon can be running per process + * using this API. + * + * @param daemon the MHD service to run + */ +void +TALER_MHD2_daemon_start (struct MHD_Daemon *daemon); + + +/** + * Stop running the event loop for MHD. + * + * @return the daemon that we were previously running, + * or NULL if none was active + */ +struct MHD_Daemon * +TALER_MHD2_daemon_stop (void); + + +/** + * Trigger MHD daemon that is running. Needed when + * a request was resumed. + */ +void +TALER_MHD2_daemon_trigger (void); + + +/** + * Prepared responses for legal documents + * (terms of service, privacy policy). + */ +struct TALER_MHD2_Legal; + + +/** + * Load set of legal documents as specified in @a cfg in section @a section + * where the Etag is given under the @a tagoption and the directory under + * the @a diroption. + * + * @param cfg configuration to use + * @param section section to load values from + * @param diroption name of the option with the + * path to the legal documents + * @param tagoption name of the files to use + * for the legal documents and the Etag + * @return NULL on error + */ +struct TALER_MHD2_Legal * +TALER_MHD2_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + const char *diroption, + const char *tagoption); + + +/** + * Free set of legal documents + * + * @param legal legal documents to free + */ +void +TALER_MHD2_legal_free (struct TALER_MHD2_Legal *legal); + + +/** + * Generate a response with a legal document in + * the format and language of the user's choosing. + * + * @param conn HTTP request to handle + * @param legal legal document to serve + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_legal (struct MHD_Request *conn, + struct TALER_MHD2_Legal *legal); + + +/** + * Send back a "204 No Content" response with headers + * for the CORS pre-flight request. + * + * @param request the MHD request + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_reply_cors_preflight (struct MHD_Request *request); + + +/** + * Load SPA files from @a dir + * + * @param pd project data to use to determine the parent directory + * @param dir directory suffix to append to our data directory with the location of the files of the SPA + * @return handle to serve static files from @a dir + */ +struct TALER_MHD2_Spa * +TALER_MHD2_spa_load (const struct GNUNET_OS_ProjectData *pd, + const char *dir); + + +/** + * Release resources used by SPA handler. + * + * @param[in] spa data structure to release + */ +void +TALER_MHD2_spa_free (struct TALER_MHD2_Spa *spa); + + +/** + * Handle HTTP request for files in a @a spa. Generates + * a 404 if no file at @a path does exists. + * + * @param spa the SPA to serve files from + * @param request HTTP request to return data on + * @param path request path to match against the @a spa + * @return MHD action + */ +const struct MHD_Action * +TALER_MHD2_spa_handler (const struct TALER_MHD2_Spa *spa, + struct MHD_Request *request, + const char *path); + + +#endif diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h @@ -62,6 +62,43 @@ enum TALER_MHD_GlobalOptions }; +#if MHD_VERSION < 0x00097701 +#define MHD_create_response_from_buffer_static(s, b) \ + MHD_create_response_from_buffer (s, \ + (const char *) b, \ + MHD_RESPMEM_PERSISTENT) +#endif + + +/** + * Find out if an MHD connection is using HTTPS (either + * directly or via proxy). + * + * @param connection MHD connection + * @returns #GNUNET_YES if the MHD connection is using https, + * #GNUNET_NO if the MHD connection is using http, + * #GNUNET_SYSERR if the connection type couldn't be determined + */ +enum GNUNET_GenericReturnValue +TALER_mhd_is_https (struct MHD_Connection *connection); + + +/** + * Convert query argument to @a yna value. + * + * @param connection connection to take query argument from + * @param arg argument to try for + * @param default_val value to assign if the argument is not present + * @param[out] yna value to set + * @return true on success, false if the parameter was malformed + */ +bool +TALER_MHD_arg_to_yna (struct MHD_Connection *connection, + const char *arg, + enum TALER_EXCHANGE_YesNoAll default_val, + enum TALER_EXCHANGE_YesNoAll *yna); + + /** * Set global options for response generation within libtalermhd. * @@ -134,23 +171,6 @@ TALER_MHD_reply_json_steal (struct MHD_Connection *connection, /** * Function to call to handle the request by building a JSON - * reply from a format string and varargs. - * - * @param connection the MHD connection to handle - * @param response_code HTTP response code to use - * @param fmt format string for pack - * @param ... varargs - * @return MHD result code - */ -MHD_RESULT -TALER_MHD_reply_json_pack (struct MHD_Connection *connection, - unsigned int response_code, - const char *fmt, - ...); - - -/** - * Function to call to handle the request by building a JSON * reply from varargs. * * @param connection the MHD connection to handle @@ -229,18 +249,6 @@ TALER_MHD_make_json_steal (json_t *json); /** * Make JSON response object. * - * @param fmt format string for pack - * @param ... varargs - * @return MHD response object - */ -struct MHD_Response * -TALER_MHD_make_json_pack (const char *fmt, - ...); - - -/** - * Make JSON response object. - * * @param ... varargs * @return MHD response object */ @@ -517,10 +525,10 @@ TALER_MHD_parse_request_arg_timestamp (struct MHD_Connection *connection, */ #define TALER_MHD_parse_request_yna(connection,name,def,ret) \ do { \ - if (! (TALER_arg_to_yna (connection, \ - name, \ - def, \ - ret)) ) \ + if (! (TALER_MHD_arg_to_yna (connection, \ + name, \ + def, \ + ret)) ) \ { \ GNUNET_break_op (0); \ return TALER_MHD_reply_with_error ( \ diff --git a/src/include/taler_util.h b/src/include/taler_util.h @@ -28,16 +28,9 @@ #define __TALER_UTIL_LIB_H_INSIDE__ #include <gnunet/gnunet_util_lib.h> -#include <microhttpd.h> #include "taler_amount_lib.h" #include "taler_crypto_lib.h" -#if MHD_VERSION < 0x00097701 -#define MHD_create_response_from_buffer_static(s, b) \ - MHD_create_response_from_buffer (s, \ - (const char *) b, \ - MHD_RESPMEM_PERSISTENT) -#endif /** * Version of the Taler API, in hex. @@ -453,19 +446,6 @@ TALER_pattern_matches (const char *pattern, /** - * Find out if an MHD connection is using HTTPS (either - * directly or via proxy). - * - * @param connection MHD connection - * @returns #GNUNET_YES if the MHD connection is using https, - * #GNUNET_NO if the MHD connection is using http, - * #GNUNET_SYSERR if the connection type couldn't be determined - */ -enum GNUNET_GenericReturnValue -TALER_mhd_is_https (struct MHD_Connection *connection); - - -/** * Make an absolute URL with query parameters. * * If a 'value' is given as NULL, both the key and the value are skipped. Note @@ -532,21 +512,6 @@ TALER_url_absolute_raw_va (const char *proto, /** - * Make an absolute URL for a given MHD connection. - * - * @param connection the connection to get the URL for - * @param path path of the url - * @param ... NULL-terminated key-value pairs (char *) for query parameters, - * the value will be url-encoded - * @returns the URL, must be freed with #GNUNET_free - */ -char * -TALER_url_absolute_mhd (struct MHD_Connection *connection, - const char *path, - ...); - - -/** * Obtain the payment method from a @a payto_uri * * @param payto_uri the URL to parse @@ -746,22 +711,6 @@ enum TALER_EXCHANGE_YesNoAll /** - * Convert query argument to @a yna value. - * - * @param connection connection to take query argument from - * @param arg argument to try for - * @param default_val value to assign if the argument is not present - * @param[out] yna value to set - * @return true on success, false if the parameter was malformed - */ -bool -TALER_arg_to_yna (struct MHD_Connection *connection, - const char *arg, - enum TALER_EXCHANGE_YesNoAll default_val, - enum TALER_EXCHANGE_YesNoAll *yna); - - -/** * Convert YNA value to a string. * * @param yna value to convert diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am @@ -10,6 +10,7 @@ lib_LTLIBRARIES = \ libtalermhd.la libtalermhd_la_SOURCES = \ + mhd.c \ mhd_config.c \ mhd_legal.c \ mhd_parsing.c \ @@ -23,6 +24,7 @@ libtalermhd_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ -lgnunetjson \ + -lgnunetmhd \ -lgnunetutil \ -lmicrohttpd \ -ljansson \ @@ -34,12 +36,9 @@ lib_LTLIBRARIES += \ libtalermhd2.la libtalermhd2_la_SOURCES = \ - mhd2_config.c \ - mhd2_legal.c \ - mhd2_parsing.c \ + mhd_config.c \ mhd2_responses.c \ - mhd2_run.c \ - mhd2_spa.c + mhd2_run.c libtalermhd2_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined diff --git a/src/mhd/mhd.c b/src/mhd/mhd.c @@ -0,0 +1,117 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 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/> +*/ +/** + * @file mhd.c + * @brief MHD utility functions (used by the merchant backend) + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_mhd_lib.h" + +/** + * Find out if an MHD connection is using HTTPS (either + * directly or via proxy). + * + * @param connection MHD connection + * @returns #GNUNET_YES if the MHD connection is using https, + * #GNUNET_NO if the MHD connection is using http, + * #GNUNET_SYSERR if the connection type couldn't be determined + */ +enum GNUNET_GenericReturnValue +TALER_mhd_is_https (struct MHD_Connection *connection) +{ + const union MHD_ConnectionInfo *ci; + const union MHD_DaemonInfo *di; + const char *forwarded_proto = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Proto"); + + if (NULL != forwarded_proto) + { + if (0 == strcasecmp (forwarded_proto, + "https")) + return GNUNET_YES; + if (0 == strcasecmp (forwarded_proto, + "http")) + return GNUNET_NO; + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* likely not reverse proxy, figure out if we are + http by asking MHD */ + ci = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_DAEMON); + if (NULL == ci) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + di = MHD_get_daemon_info (ci->daemon, + MHD_DAEMON_INFO_FLAGS); + if (NULL == di) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (0 != (di->flags & MHD_USE_TLS)) + return GNUNET_YES; + return GNUNET_NO; +} + + +/** + * Convert query argument to @a yna value. + * + * @param connection connection to take query argument from + * @param arg argument to try for + * @param default_val value to assign if the argument is not present + * @param[out] yna value to set + * @return true on success, false if the parameter was malformed + */ +bool +TALER_MHD_arg_to_yna (struct MHD_Connection *connection, + const char *arg, + enum TALER_EXCHANGE_YesNoAll default_val, + enum TALER_EXCHANGE_YesNoAll *yna) +{ + const char *str; + + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + arg); + if (NULL == str) + { + *yna = default_val; + return true; + } + if (0 == strcasecmp (str, "yes")) + { + *yna = TALER_EXCHANGE_YNA_YES; + return true; + } + if (0 == strcasecmp (str, "no")) + { + *yna = TALER_EXCHANGE_YNA_NO; + return true; + } + if (0 == strcasecmp (str, "all")) + { + *yna = TALER_EXCHANGE_YNA_ALL; + return true; + } + return false; +} diff --git a/src/mhd/mhd2_responses.c b/src/mhd/mhd2_responses.c @@ -0,0 +1,449 @@ +/* + This file is part of TALER + Copyright (C) 2014-2025 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd2_responses.c + * @brief API for generating HTTP replies + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <zlib.h> +#include "taler_util.h" +#include "taler_mhd2_lib.h" + + +/** + * Global options for response generation. + */ +static enum TALER_MHD2_GlobalOptions TM_go; + + +void +TALER_MHD2_setup (enum TALER_MHD2_GlobalOptions go) +{ + TM_go = go; +} + + +void +TALER_MHD2_add_global_headers (struct MHD_Response *response) +{ + if (0 != (TM_go & TALER_MHD2_GO_FORCE_CONNECTION_CLOSE)) + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_CONNECTION, + "close")); + /* The wallet, operating from a background page, needs CORS to + be disabled otherwise browsers block access. */ + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + "*")); + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Expose-Headers", + "*")); +} + + +bool +TALER_MHD2_can_compress (struct MHD_Request *request) +{ + const struct MHD_StringNullable *aeb; + const char *ae; + const char *de; + + if (0 != (TM_go & TALER_MHD2_GO_DISABLE_COMPRESSION)) + return MHD_NO; + aeb = MHD_request_get_value (request, + MHD_VK_HEADER, + MHD_HTTP_HEADER_ACCEPT_ENCODING); + ae = aeb->cstr; + if (NULL == ae) + return false; + if (0 == strcmp (ae, + "*")) + return true; + de = strstr (ae, + "deflate"); + if (NULL == de) + return false; + if ( ( (de == ae) || + (de[-1] == ',') || + (de[-1] == ' ') ) && + ( (de[strlen ("deflate")] == '\0') || + (de[strlen ("deflate")] == ',') || + (de[strlen ("deflate")] == ';') ) ) + return true; + return false; +} + + +bool +TALER_MHD2_body_compress (void **buf, + size_t *buf_size) +{ + Bytef *cbuf; + uLongf cbuf_size; + int ret; + + cbuf_size = compressBound (*buf_size); + cbuf = malloc (cbuf_size); + if (NULL == cbuf) + return false; + ret = compress (cbuf, + &cbuf_size, + (const Bytef *) *buf, + *buf_size); + if ( (Z_OK != ret) || + (cbuf_size >= *buf_size) ) + { + /* compression failed */ + free (cbuf); + return false; + } + free (*buf); + *buf = (void *) cbuf; + *buf_size = (size_t) cbuf_size; + return true; +} + + +struct MHD_Response * +TALER_MHD2_make_json (enum MHD_HTTP_StatusCode sc, + const json_t *json) +{ + struct MHD_Response *response; + char *json_str; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + GNUNET_break (0); + return NULL; + } + response = MHD_response_from_buffer (sc, + strlen (json_str), + json_str, + &free, + json_str); + if (NULL == response) + { + free (json_str); + GNUNET_break (0); + return NULL; + } + TALER_MHD2_add_global_headers (response); + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + return response; +} + + +struct MHD_Response * +TALER_MHD2_make_json_steal (enum MHD_HTTP_StatusCode sc, + json_t *json) +{ + struct MHD_Response *res; + + res = TALER_MHD2_make_json (sc, + json); + json_decref (json); + return res; +} + + +const struct MHD_Action * +TALER_MHD2_reply_json (struct MHD_Request *request, + const json_t *json, + enum MHD_HTTP_StatusCode sc) +{ + struct MHD_Response *response; + void *json_str; + size_t json_len; + bool is_compressed; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + /** + * This log helps to figure out which + * function called this one and assert-failed. + */ + TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n", + sc); + + GNUNET_assert (0); + return NULL; + } + json_len = strlen (json_str); + /* try to compress the body */ + is_compressed = MHD_NO; + if (TALER_MHD2_can_compress (request)) + is_compressed = TALER_MHD2_body_compress (&json_str, + &json_len); + response = MHD_response_from_buffer (sc, + json_len, + json_str, + &free, + json_str); + if (NULL == response) + { + free (json_str); + GNUNET_break (0); + return NULL; + } + TALER_MHD2_add_global_headers (response); + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + if (is_compressed) + { + /* Need to indicate to client that body is compressed */ + if (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_response_destroy (response); + return NULL; + } + } + + return MHD_action_from_response (request, + response); +} + + +const struct MHD_Action * +TALER_MHD2_reply_json_steal (struct MHD_Request *request, + json_t *json, + enum MHD_HTTP_StatusCode sc) +{ + const struct MHD_Action *ret; + + ret = TALER_MHD2_reply_json (request, + json, + sc); + json_decref (json); + return ret; +} + + +const struct MHD_Action * +TALER_MHD2_reply_cors_preflight (struct MHD_Request *request) +{ + struct MHD_Response *response; + + response = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT); + if (NULL == response) + return NULL; + /* This adds the Access-Control-Allow-Origin header. + * All endpoints of the exchange allow CORS. */ + TALER_MHD2_add_global_headers (response); + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Allow-Headers", + "*")); + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Allow-Methods", + "*")); + return MHD_action_from_response (request, + response); +} + + +struct MHD_Response * +TALER_MHD2_make_error (enum TALER_ErrorCode ec, + const char *detail) +{ + return TALER_MHD2_MAKE_JSON_PACK ( + TALER_ErrorCode_get_http_status (ec), + TALER_MHD2_PACK_EC (ec), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", detail))); +} + + +const struct MHD_Action * +TALER_MHD2_reply_with_error (struct MHD_Request *request, + enum MHD_HTTP_StatusCode sc, + enum TALER_ErrorCode ec, + const char *detail) +{ + return TALER_MHD2_REPLY_JSON_PACK ( + request, + sc, + TALER_MHD2_PACK_EC (ec), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", detail))); +} + + +const struct MHD_Action * +TALER_MHD2_reply_with_ec (struct MHD_Request *request, + enum TALER_ErrorCode ec, + const char *detail) +{ + unsigned int hc + = TALER_ErrorCode_get_http_status (ec); + + if ( (0 == hc) || + (UINT_MAX == hc) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid Taler error code %u provided for response!\n", + (unsigned int) ec); + hc = MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + return TALER_MHD2_reply_with_error (request, + (enum MHD_HTTP_StatusCode) hc, + ec, + detail); +} + + +const struct MHD_Action * +TALER_MHD2_reply_request_too_large (struct MHD_Request *request) +{ + return TALER_MHD2_reply_with_error ( + request, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT, + NULL); +} + + +const struct MHD_Action * +TALER_MHD2_reply_agpl (struct MHD_Request *request, + const char *url) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + + response = MHD_response_from_buffer_static (MHD_HTTP_STATUS_FOUND, + strlen (agpl), + (void *) agpl); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD2_add_global_headers (response); + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + if (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_LOCATION, + url)) + { + GNUNET_break (0); + MHD_response_destroy (response); + return NULL; + } + return MHD_action_from_response (request, + response); +} + + +const struct MHD_Action * +TALER_MHD2_reply_static (struct MHD_Request *request, + enum MHD_HTTP_StatusCode sc, + const char *mime_type, + const char *body, + size_t body_size) +{ + struct MHD_Response *response; + + response = MHD_response_from_buffer_static (sc, + body_size, + body); + if (NULL == response) + { + GNUNET_break (0); + return NULL; + } + TALER_MHD2_add_global_headers (response); + if (NULL != mime_type) + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + mime_type)); + return MHD_action_from_response (request, + response); +} + + +void +TALER_MHD2_get_date_string (struct GNUNET_TIME_Absolute at, + char date[128]) +{ + static const char *const days[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char *const mons[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec"}; + struct tm now; + time_t t; +#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ + ! defined(HAVE_GMTIME_R) + struct tm*pNow; +#endif + + date[0] = 0; + t = (time_t) (at.abs_value_us / 1000LL / 1000LL); +#if defined(HAVE_C11_GMTIME_S) + if (NULL == gmtime_s (&t, &now)) + return; +#elif defined(HAVE_W32_GMTIME_S) + if (0 != gmtime_s (&now, &t)) + return; +#elif defined(HAVE_GMTIME_R) + if (NULL == gmtime_r (&t, &now)) + return; +#else + pNow = gmtime (&t); + if (NULL == pNow) + return; + now = *pNow; +#endif + sprintf (date, + "%3s, %02u %3s %04u %02u:%02u:%02u GMT", + days[now.tm_wday % 7], + (unsigned int) now.tm_mday, + mons[now.tm_mon % 12], + (unsigned int) (1900 + now.tm_year), + (unsigned int) now.tm_hour, + (unsigned int) now.tm_min, + (unsigned int) now.tm_sec); +} + + +/* end of mhd_responses.c */ diff --git a/src/mhd/mhd2_run.c b/src/mhd/mhd2_run.c @@ -0,0 +1,292 @@ +/* + This file is part of TALER + Copyright (C) 2019-2025 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd2_run.c + * @brief API for running an MHD daemon with the + * GNUnet scheduler + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#define MHD_APP_SOCKET_CNTX_TYPE struct SocketContext +#include <microhttpd2.h> +#include "taler_mhd2_lib.h" + + +/** + * Context to track whatever MHD wants us to wait for. + */ +struct SocketContext +{ + /** + * Task for this socket. + */ + struct GNUNET_SCHEDULER_Task *mhd_rtask; + + /** + * Task for this socket. + */ + struct GNUNET_SCHEDULER_Task *mhd_wtask; + + /** + * Internal handle to pass to MHD when ready. + */ + struct MHD_EventUpdateContext *ecb_cntx; + + /** + * Socket to watch for. + */ + struct GNUNET_NETWORK_Handle *fd; +}; + + +/** + * Set to true if we should immediately MHD_run() again. + */ +static bool triggered; + +/** + * Task running the HTTP server. + */ +static struct GNUNET_SCHEDULER_Task *mhd_task; + +/** + * The MHD daemon we are running. + */ +static struct MHD_Daemon *mhd; + + +/** + * Function that queries MHD's select sets and + * starts the task waiting for them. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void); + + +/** + * Trigger MHD on timeout. + * + * @param cls not used + */ +static void +handle_timeout (void *cls) +{ + (void) cls; + mhd_task = prepare_daemon (); +} + + +/** + * Function that queries MHD's select sets and + * starts the task waiting for them. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void) +{ + uint_fast64_t next_max_wait; + + GNUNET_break (MHD_SC_OK == + MHD_deamon_process_reg_events (mhd, + &next_max_wait)); + if (MHD_WAIT_INDEFINITELY == next_max_wait) + return NULL; + return GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_MICROSECONDS, + next_max_wait), + &handle_timeout, + NULL); +} + + +/** + * Call MHD to process pending requests and then go back + * and schedule the next run. + * + * @param cls NULL + */ +static void +run_daemon (void *cls) +{ + (void) cls; + mhd_task = NULL; + do { + triggered = false; + GNUNET_break (MHD_SC_OK == + MHD_daemon_process_nonblocking (mhd)); + } while (triggered); + mhd_task = prepare_daemon (); +} + + +/** + * Called whenever MHD should process read-events on the socket. + * + * @param cls a `struct SocketContext` + */ +static void +mhd_rready (void *cls) +{ + struct SocketContext *sc = cls; + + sc->mhd_rtask = NULL; + MHD_daemon_event_update (mhd, + sc->ecb_cntx, + MHD_FD_STATE_RECV); +} + + +/** + * Called whenever MHD should process write-events on the socket. + * + * @param cls a `struct SocketContext` + */ +static void +mhd_wready (void *cls) +{ + struct SocketContext *sc = cls; + + sc->mhd_wtask = NULL; + MHD_daemon_event_update (mhd, + sc->ecb_cntx, + MHD_FD_STATE_SEND); +} + + +/** + * Callback for registration/de-registration of the sockets to watch. + * + * @param cls the closure + * @param fd the socket to watch + * @param watch_for the states of the @a fd to watch, if set to + * #MHD_FD_STATE_NONE the socket must be de-registred + * @param app_cntx_old the old application defined context for the socket, + * NULL if @a fd socket was not registered before + * @param ecb_cntx the context handle to be used + * with #MHD_daemon_event_update() + * @return NULL if error (to connection will be aborted), + * or the new socket context + * @ingroup event + */ +static MHD_APP_SOCKET_CNTX_TYPE * +socket_registration_update ( + void *cls, + MHD_Socket fd, + enum MHD_FdState watch_for, + MHD_APP_SOCKET_CNTX_TYPE *app_cntx_old, + struct MHD_EventUpdateContext *ecb_cntx) +{ + (void) cls; + if (NULL == app_cntx_old) + { + app_cntx_old = GNUNET_new (struct SocketContext); + app_cntx_old->ecb_cntx = ecb_cntx; + app_cntx_old->fd = GNUNET_NETWORK_socket_box_native (fd); + } + if (MHD_FD_STATE_NONE == watch_for) + { + if (NULL != app_cntx_old->mhd_rtask) + GNUNET_SCHEDULER_cancel (app_cntx_old->mhd_rtask); + if (NULL != app_cntx_old->mhd_wtask) + GNUNET_SCHEDULER_cancel (app_cntx_old->mhd_wtask); + GNUNET_NETWORK_socket_free_memory_only_ (app_cntx_old->fd); + GNUNET_free (app_cntx_old); + return NULL; + } + if ( (MHD_FD_STATE_RECV & watch_for) && + (NULL == app_cntx_old->mhd_rtask) ) + { + app_cntx_old->mhd_rtask + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + app_cntx_old->fd, + &mhd_rready, + app_cntx_old); + } + if ( (MHD_FD_STATE_SEND & watch_for) && + (NULL == app_cntx_old->mhd_wtask) ) + { + app_cntx_old->mhd_wtask + = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL, + app_cntx_old->fd, + &mhd_wready, + app_cntx_old); + } + if ( (0 == (MHD_FD_STATE_RECV & watch_for)) && + (NULL != app_cntx_old->mhd_rtask) ) + { + GNUNET_SCHEDULER_cancel (app_cntx_old->mhd_rtask); + app_cntx_old->mhd_rtask = NULL; + } + if ( (0 == (MHD_FD_STATE_SEND & watch_for)) && + (NULL != app_cntx_old->mhd_wtask) ) + { + GNUNET_SCHEDULER_cancel (app_cntx_old->mhd_wtask); + app_cntx_old->mhd_wtask = NULL; + } + return app_cntx_old; +} + + +void +TALER_MHD2_daemon_start (struct MHD_Daemon *daemon) +{ + GNUNET_assert (NULL == mhd); + GNUNET_assert (MHD_SC_OK == + MHD_DAEMON_SET_OPTIONS ( + daemon, + MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL ( + &socket_registration_update, + NULL))); + mhd = daemon; + mhd_task = prepare_daemon (); +} + + +struct MHD_Daemon * +TALER_MHD2_daemon_stop (void) +{ + struct MHD_Daemon *ret; + + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = NULL; + } + ret = mhd; + mhd = NULL; + return ret; +} + + +void +TALER_MHD2_daemon_trigger (void) +{ + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon, + NULL); + } + else + { + triggered = true; + } +} + + +/* end of mhd2_run.c */ diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c @@ -23,6 +23,7 @@ #include "platform.h" #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_mhd_lib.h> #include "taler_json_lib.h" #include "taler_mhd_lib.h" @@ -34,17 +35,17 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, size_t *upload_data_size, json_t **json) { - enum GNUNET_JSON_PostResult pr; - - pr = GNUNET_JSON_post_parser (TALER_MHD_REQUEST_BUFFER_MAX, - connection, - con_cls, - upload_data, - upload_data_size, - json); + enum GNUNET_MHD_PostResult pr; + + pr = GNUNET_MHD_post_parser (TALER_MHD_REQUEST_BUFFER_MAX, + connection, + con_cls, + upload_data, + upload_data_size, + json); switch (pr) { - case GNUNET_JSON_PR_OUT_OF_MEMORY: + case GNUNET_MHD_PR_OUT_OF_MEMORY: GNUNET_break (NULL == *json); return (MHD_NO == TALER_MHD_reply_with_error ( @@ -53,15 +54,15 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY, NULL)) ? GNUNET_SYSERR : GNUNET_NO; - case GNUNET_JSON_PR_CONTINUE: + case GNUNET_MHD_PR_CONTINUE: GNUNET_break (NULL == *json); return GNUNET_YES; - case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + case GNUNET_MHD_PR_REQUEST_TOO_LARGE: GNUNET_break (NULL == *json); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Closing connection, upload too large\n"); return GNUNET_SYSERR; - case GNUNET_JSON_PR_JSON_INVALID: + case GNUNET_MHD_PR_JSON_INVALID: GNUNET_break (NULL == *json); return (MHD_YES == TALER_MHD_reply_with_error (connection, @@ -69,7 +70,7 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, TALER_EC_GENERIC_JSON_INVALID, NULL)) ? GNUNET_NO : GNUNET_SYSERR; - case GNUNET_JSON_PR_SUCCESS: + case GNUNET_MHD_PR_SUCCESS: GNUNET_break (NULL != *json); return GNUNET_YES; } @@ -82,7 +83,7 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection, void TALER_MHD_parse_post_cleanup_callback (void *con_cls) { - GNUNET_JSON_post_parser_cleanup (con_cls); + GNUNET_MHD_post_parser_cleanup (con_cls); } diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c @@ -285,87 +285,6 @@ TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection) } -MHD_RESULT -TALER_MHD_reply_json_pack (struct MHD_Connection *connection, - unsigned int response_code, - const char *fmt, - ...) -{ - json_t *json; - json_error_t jerror; - - { - va_list argp; - - va_start (argp, - fmt); - json = json_vpack_ex (&jerror, - 0, - fmt, - argp); - va_end (argp); - } - - if (NULL == json) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to pack JSON with format `%s': %s\n", - fmt, - jerror.text); - GNUNET_break (0); - return MHD_NO; - } - - { - MHD_RESULT ret; - - ret = TALER_MHD_reply_json (connection, - json, - response_code); - json_decref (json); - return ret; - } -} - - -struct MHD_Response * -TALER_MHD_make_json_pack (const char *fmt, - ...) -{ - json_t *json; - json_error_t jerror; - - { - va_list argp; - - va_start (argp, fmt); - json = json_vpack_ex (&jerror, - 0, - fmt, - argp); - va_end (argp); - } - - if (NULL == json) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to pack JSON with format `%s': %s\n", - fmt, - jerror.text); - GNUNET_break (0); - return NULL; - } - - { - struct MHD_Response *response; - - response = TALER_MHD_make_json (json); - json_decref (json); - return response; - } -} - - struct MHD_Response * TALER_MHD_make_error (enum TALER_ErrorCode ec, const char *detail) diff --git a/src/util/Makefile.am b/src/util/Makefile.am @@ -99,7 +99,6 @@ libtalerutil_la_SOURCES = \ iban.c \ kyc_signatures.c \ merchant_signatures.c \ - mhd.c \ offline_signatures.c \ payto.c \ secmod_common.c secmod_common.h \ diff --git a/src/util/mhd.c b/src/util/mhd.c @@ -1,135 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 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/> -*/ -/** - * @file mhd.c - * @brief MHD utility functions (used by the merchant backend) - * @author Christian Grothoff - */ -#include "platform.h" -#include "taler_util.h" - - -/** - * Find out if an MHD connection is using HTTPS (either - * directly or via proxy). - * - * @param connection MHD connection - * @returns #GNUNET_YES if the MHD connection is using https, - * #GNUNET_NO if the MHD connection is using http, - * #GNUNET_SYSERR if the connection type couldn't be determined - */ -enum GNUNET_GenericReturnValue -TALER_mhd_is_https (struct MHD_Connection *connection) -{ - const union MHD_ConnectionInfo *ci; - const union MHD_DaemonInfo *di; - const char *forwarded_proto = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - "X-Forwarded-Proto"); - - if (NULL != forwarded_proto) - { - if (0 == strcasecmp (forwarded_proto, - "https")) - return GNUNET_YES; - if (0 == strcasecmp (forwarded_proto, - "http")) - return GNUNET_NO; - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* likely not reverse proxy, figure out if we are - http by asking MHD */ - ci = MHD_get_connection_info (connection, - MHD_CONNECTION_INFO_DAEMON); - if (NULL == ci) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - di = MHD_get_daemon_info (ci->daemon, - MHD_DAEMON_INFO_FLAGS); - if (NULL == di) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (0 != (di->flags & MHD_USE_TLS)) - return GNUNET_YES; - return GNUNET_NO; -} - - -/** - * Make an absolute URL for a given MHD connection. - * - * @param connection the connection to get the URL for - * @param path path of the url - * @param ... NULL-terminated key-value pairs (char *) for query parameters, - * the value will be url-encoded - * @returns the URL, must be freed with #GNUNET_free - */ -char * -TALER_url_absolute_mhd (struct MHD_Connection *connection, - const char *path, - ...) -{ - /* By default we assume we're running under HTTPS */ - const char *proto; - const char *host; - const char *forwarded_host; - const char *prefix; - va_list args; - char *result; - - if (GNUNET_YES == TALER_mhd_is_https (connection)) - proto = "https"; - else - proto = "http"; - - host = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - "Host"); - forwarded_host = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - "X-Forwarded-Host"); - - prefix = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - "X-Forwarded-Prefix"); - if (NULL == prefix) - prefix = ""; - - if (NULL != forwarded_host) - host = forwarded_host; - - if (NULL == host) - { - /* Should never happen, at last the host header should be defined */ - GNUNET_break (0); - return NULL; - } - - va_start (args, - path); - result = TALER_url_absolute_raw_va (proto, - host, - prefix, - path, - args); - va_end (args); - return result; -} diff --git a/src/util/yna.c b/src/util/yna.c @@ -23,50 +23,6 @@ /** - * Convert query argument to @a yna value. - * - * @param connection connection to take query argument from - * @param arg argument to try for - * @param default_val value to assign if the argument is not present - * @param[out] yna value to set - * @return true on success, false if the parameter was malformed - */ -bool -TALER_arg_to_yna (struct MHD_Connection *connection, - const char *arg, - enum TALER_EXCHANGE_YesNoAll default_val, - enum TALER_EXCHANGE_YesNoAll *yna) -{ - const char *str; - - str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - arg); - if (NULL == str) - { - *yna = default_val; - return true; - } - if (0 == strcasecmp (str, "yes")) - { - *yna = TALER_EXCHANGE_YNA_YES; - return true; - } - if (0 == strcasecmp (str, "no")) - { - *yna = TALER_EXCHANGE_YNA_NO; - return true; - } - if (0 == strcasecmp (str, "all")) - { - *yna = TALER_EXCHANGE_YNA_ALL; - return true; - } - return false; -} - - -/** * Convert YNA value to a string. * * @param yna value to convert