From 698519e581cc5822ba68eb687e19ab8f10f5a093 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 25 Nov 2015 16:40:45 +0100 Subject: major refactoring of taler-merchant-httpd_pay, compiles but cannot yet work (uninitialized fields, some logic still broken, etc.) --- src/backend/Makefile.am | 1 + src/backend/taler-merchant-httpd.c | 236 +--------- src/backend/taler-merchant-httpd.h | 112 +---- src/backend/taler-merchant-httpd_contract.c | 19 +- src/backend/taler-merchant-httpd_pay.c | 681 ++++++++++++--------------- src/backend/taler-merchant-httpd_responses.c | 107 ++++- src/backend/taler-merchant-httpd_responses.h | 48 ++ 7 files changed, 501 insertions(+), 703 deletions(-) diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index b1936990..8d730f3e 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -9,6 +9,7 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \ taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \ taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \ + taler-merchant-httpd_mints.c taler-merchant-httpd_mints.h \ taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \ taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 8a32b2ba..83c902fd 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -19,8 +19,8 @@ * @brief HTTP serving layer intended to perform crypto-work and * communication with the mint * @author Marcello Stanisci + * @author Christian Grothoff */ - #include "platform.h" #include #include @@ -34,6 +34,7 @@ #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_contract.h" +#include "taler-merchant-httpd_mints.h" #include "taler-merchant-httpd_pay.h" @@ -48,12 +49,16 @@ struct json_t *j_wire; */ struct GNUNET_HashCode h_wire; - /** * Merchant's private key */ struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; +/** + * Merchant's public key + */ +struct TALER_MerchantPublicKeyP pubkey; + /** * Our hostname */ @@ -80,11 +85,6 @@ struct GNUNET_TIME_Relative edate_delay; */ char *TMH_merchant_currency_string; -/** - * Trusted mints (FIXME: they are NOT all trusted!). - */ -struct MERCHANT_Mint **mints; - /** * Active auditors */ @@ -100,11 +100,6 @@ static struct GNUNET_SCHEDULER_Task *shutdown_task; */ static struct GNUNET_SCHEDULER_Task *mhd_task; -/** - * Length of the #mints array. - */ -unsigned int nmints; - /** * The number of active auditors */ @@ -187,15 +182,9 @@ url_handler (void *cls, "Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", 0, &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - /* Further test page */ - { "/hello", MHD_HTTP_METHOD_GET, "text/plain", - "Hello, Customer.\n", 0, - &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - { "/contract", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_contract, MHD_HTTP_OK }, - { "/contract", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, @@ -203,33 +192,24 @@ url_handler (void *cls, { "/pay", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_pay, MHD_HTTP_OK }, - { "/pay", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - {NULL, NULL, NULL, NULL, 0, 0 } }; - static struct TMH_RequestHandler h404 = { "", NULL, "text/html", "404: not found", 0, &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND }; - - /* Compiler complains about non returning a value in a non-void - declared function: the FIX is to return what the handler for - a particular URL returns */ - struct TMH_RequestHandler *rh; unsigned int i; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Handling request for URL '%s'\n", + "Handling request for URL `%s'\n", url); - for (i=0;NULL != handlers[i].url;i++) { rh = &handlers[i]; @@ -249,43 +229,6 @@ url_handler (void *cls, con_cls, upload_data, upload_data_size); - -} - - -/** - * Function called with information about who is auditing - * a particular mint and what key the mint is using. - * - * @param cls closure, will be 'struct MERCHANT_Mint' so that - * when this function gets called, it will change the flag 'pending' - * to 'false'. Note: 'keys' is automatically saved inside the mint's - * handle, which is contained inside 'struct MERCHANT_Mint', when - * this callback is called. Thus, once 'pending' turns 'false', - * it is safe to call 'TALER_MINT_get_keys()' on the mint's handle, - * in order to get the "good" keys. - * - * @param keys information about the various keys used - * by the mint - */ -static void -keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) -{ - /* HOT UPDATE: the merchants need the denomination keys! - Because it wants to (firstly) verify the deposit confirmation - sent by the mint, and the signed blob depends (among the - other things) on the coin's deposit fee. That information - is never communicated by the wallet to the merchant. - Again, the merchant needs it because it wants to verify that - the wallet didn't exceede the limit imposed by the merchant - on the total deposit fee for a purchase */ - - if (NULL != keys) - { - ((struct MERCHANT_Mint *) cls)->pending = 0; - } - else - printf ("no keys gotten\n"); } @@ -299,18 +242,6 @@ keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) static void do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { - unsigned int cnt; - - for (cnt = 0; cnt < nmints; cnt++) - { - if (NULL != mints[cnt]->conn) - TALER_MINT_disconnect (mints[cnt]->conn); - if (NULL != mints[cnt]->poller_task) - { - GNUNET_SCHEDULER_cancel (mints[cnt]->poller_task); - mints[cnt]->poller_task = NULL; - } - } if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); @@ -331,65 +262,6 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) } -/** - * Task that runs the context's event loop using the GNUnet scheduler. - * - * @param cls a `struct MERCHANT_Mint *` - * @param tc scheduler context (unused) - */ -void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct MERCHANT_Mint *mint = cls; - long timeout; - int max_fd; - fd_set read_fd_set; - fd_set write_fd_set; - fd_set except_fd_set; - struct GNUNET_NETWORK_FDSet *rs; - struct GNUNET_NETWORK_FDSet *ws; - struct GNUNET_TIME_Relative delay; - - mint->poller_task = NULL; - TALER_MINT_perform (mint->ctx); - max_fd = -1; - timeout = -1; - FD_ZERO (&read_fd_set); - FD_ZERO (&write_fd_set); - FD_ZERO (&except_fd_set); - TALER_MINT_get_select_info (mint->ctx, - &read_fd_set, - &write_fd_set, - &except_fd_set, - &max_fd, - &timeout); - if (timeout >= 0) - delay = - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout); - else - delay = GNUNET_TIME_UNIT_FOREVER_REL; - rs = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (rs, - &read_fd_set, - max_fd + 1); - ws = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (ws, - &write_fd_set, - max_fd + 1); - mint->poller_task = - GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, - delay, - rs, - ws, - &context_task, - mint); - GNUNET_NETWORK_fdset_destroy (rs); - GNUNET_NETWORK_fdset_destroy (ws); -} - - /** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the @@ -446,81 +318,18 @@ run_daemon (void *cls, /** * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. */ void -TM_trigger_daemon () +TMH_trigger_daemon () { GNUNET_SCHEDULER_cancel (mhd_task); run_daemon (NULL, NULL); } -/** - * Parses mints listed in the configuration. - * - * @param cfg the configuration - * @return the number of mints in the above array; #GNUNET_SYSERR upon error in - * parsing. - */ -static int -parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - char *mints_str; - char *token_nf; /* do no free (nf) */ - char *mint_section; - char *mint_hostname; - struct MERCHANT_Mint **r_mints; - struct MERCHANT_Mint *mint; - unsigned int cnt; - int ok; - - ok = 0; - mints_str = NULL; - token_nf = NULL; - mint_section = NULL; - mint_hostname = NULL; - r_mints = NULL; - cnt = 0; - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "merchant", - "TRUSTED_MINTS", - &mints_str)); - for (token_nf = strtok (mints_str, " "); - NULL != token_nf; - token_nf = strtok (NULL, " ")) - { - GNUNET_assert (0 < GNUNET_asprintf (&mint_section, - "mint-%s", token_nf)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - mint_section, - "HOSTNAME", - &mint_hostname)); - mint = GNUNET_new (struct MERCHANT_Mint); - mint->hostname = mint_hostname; - GNUNET_array_append (r_mints, - cnt, - mint); - GNUNET_free (mint_section); - mint_section = NULL; - } - ok = 1; - - EXITIF_exit: - GNUNET_free_non_null (mints_str); - GNUNET_free_non_null (mint_section); - GNUNET_free_non_null (mint_hostname); - if (! ok) - { - GNUNET_free_non_null (r_mints); - return GNUNET_SYSERR; - } - mints = r_mints; - return cnt; -} - - /** * Parses auditors from the configuration. * @@ -748,16 +557,13 @@ run (void *cls, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { - unsigned int cnt; - result = GNUNET_SYSERR; shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown, NULL); EXITIF (GNUNET_SYSERR == - (nmints = - parse_mints (config))); + TMH_MINTS_parse_cfg (config)); EXITIF (GNUNET_SYSERR == (nauditors = parse_auditors (config, @@ -775,6 +581,8 @@ run (void *cls, EXITIF (NULL == (privkey = GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + GNUNET_CRYPTO_eddsa_key_get_public (privkey, + &pubkey.eddsa_pub); EXITIF (NULL == (db_conn = TALER_MERCHANTDB_connect (config))); EXITIF (GNUNET_OK != @@ -802,20 +610,6 @@ run (void *cls, &edate_delay)); - for (cnt = 0; cnt < nmints; cnt++) - { - EXITIF (NULL == (mints[cnt]->ctx = TALER_MINT_init ())); - mints[cnt]->pending = 1; - mints[cnt]->conn = TALER_MINT_connect (mints[cnt]->ctx, - mints[cnt]->hostname, - &keys_mgmt_cb, - mints[cnt], - TALER_MINT_OPTION_END); - EXITIF (NULL == mints[cnt]->conn); - mints[cnt]->poller_task = - GNUNET_SCHEDULER_add_now (&context_task, - mints[cnt]); - } mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME, port, diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index c71108c0..a66148b0 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -34,8 +34,6 @@ } while (0) - - /** * @brief Struct describing an URL and the handler for it. */ @@ -92,65 +90,6 @@ struct TMH_RequestHandler -/** - * Mint - */ -struct MERCHANT_Mint -{ - /** - * Hostname - */ - char *hostname; - - /** - * A connection to this mint - */ - struct TALER_MINT_Handle *conn; - - /** - * This mint's context (useful to the event loop) - */ - struct TALER_MINT_Context *ctx; - - /** - * Task we use to drive the interaction with this mint. - */ - struct GNUNET_SCHEDULER_Task *poller_task; - - /** - * Flag which indicates whether some HTTP transfer between - * this merchant and the mint is still ongoing - */ - int pending; - -}; - - -struct MERCHANT_WIREFORMAT_Sepa -{ - /** - * The international bank account number - */ - char *iban; - - /** - * Name of the bank account holder - */ - char *name; - - /** - *The bank identification code - */ - char *bic; - - /** - * The latest payout date when the payment corresponding to this account has - * to take place. A value of 0 indicates a transfer as soon as possible. - */ - struct GNUNET_TIME_AbsoluteNBO payout; -}; - - struct MERCHANT_Auditor { /** @@ -161,7 +100,12 @@ struct MERCHANT_Auditor }; - +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ struct TM_HandlerContext; /** @@ -175,15 +119,24 @@ typedef void (*TM_ContextCleanup)(struct TM_HandlerContext *hc); +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ struct TM_HandlerContext { + /** + * Function to execute the handler-specific cleanup of the + * (typically larger) context. + */ TM_ContextCleanup cc; }; - /** * Our wire format details in JSON format (with salt). */ @@ -195,48 +148,33 @@ extern json_t *j_wire; extern struct GNUNET_HashCode h_wire; - -extern struct MERCHANT_Auditor *auditors; -extern unsigned int nauditors; - -extern struct MERCHANT_Mint **mints; - extern struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; +extern struct TALER_MerchantPublicKeyP pubkey; -extern PGconn *db_conn; +extern struct MERCHANT_Auditor *auditors; -extern unsigned int nmints; +extern unsigned int nauditors; -extern struct GNUNET_TIME_Relative edate_delay; -void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc); +extern PGconn *db_conn; -/** - * Take the global wire details and return a JSON containing them, - * compliantly with the Taler's API. - * - * @param wire the merchant's wire details - * @param salt the nounce for hashing the wire details with - * @return JSON representation of the wire details, NULL upon errors - */ -json_t * -MERCHANT_get_wire_json (const struct MERCHANT_WIREFORMAT_Sepa *wire, - uint64_t salt); +extern struct GNUNET_TIME_Relative edate_delay; /** * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. */ void -TM_trigger_daemon (void); +TMH_trigger_daemon (void); #endif diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c index b5050666..81bf3ed9 100644 --- a/src/backend/taler-merchant-httpd_contract.c +++ b/src/backend/taler-merchant-httpd_contract.c @@ -31,6 +31,7 @@ #include #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_mints.h" #include "taler-merchant-httpd_responses.h" #include "taler_merchantdb_lib.h" #include "taler-merchant-httpd.h" @@ -57,11 +58,8 @@ MH_handler_contract (struct TMH_RequestHandler *rh, size_t *upload_data_size) { json_t *root; - json_t *trusted_mints; json_t *j_auditors; json_t *auditor; - json_t *mint; - const struct TALER_MINT_Keys *keys; int res; int cnt; struct GNUNET_HashCode h_wire; @@ -82,21 +80,6 @@ MH_handler_contract (struct TMH_RequestHandler *rh, /* Generate preferred mint(s) array. */ - trusted_mints = json_array (); - for (cnt = 0; cnt < nmints; cnt++) - { - if (! mints[cnt]->pending) - { - keys = TALER_MINT_get_keys (mints[cnt]->conn); - mint = json_pack ("{s:s, s:o}", - "url", mints[cnt]->hostname, - "master_pub", - TALER_json_from_data - (&keys->master_pub.eddsa_pub, - sizeof (keys->master_pub.eddsa_pub))); - json_array_append_new (trusted_mints, mint); - } - } j_auditors = json_array (); for (cnt = 0; cnt < nauditors; cnt++) { diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index 23aa178d..a8d85cc2 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -32,28 +32,64 @@ #include "taler-merchant-httpd_parsing.h" #include "taler-merchant-httpd_responses.h" #include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_mints.h" #include "taler_merchantdb_lib.h" /** - * Outcome of a /deposit request for a coin. Typically forming an array enclosed - * into the unique PayContext + * Information we keep for an individual call to the /pay handler. + */ +struct PayContext; + + +/** + * Information kept during a /pay request for each coin. */ struct MERCHANT_DepositConfirmation { + + /** + * Reference to the main PayContext + */ + struct PayContext *pc; + /** - * Reference to the per-deposit-handler Context. Needed by the - * cleanup function to get it freed + * */ - struct DepositCallbackContext *dcc; + struct TALER_MINT_DepositHandle *dh; /** * The mint's response body (JSON). Mainly useful in case * some callback needs to send back to the to the wallet the - * outcome of an erroneous coin + * outcome of an erroneous coin. DEAD? */ json_t *proof; + /** + * Denomination for this coin. + */ + struct TALER_DenominationPublicKey denom; + + /** + * + */ + struct TALER_Amount percoin_amount; + + /** + * + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * + */ + struct TALER_DenominationSignature ub_sig; + + /** + * + */ + struct TALER_CoinSpendSignatureP coin_sig; + /** * True if this coin's outcome has been read from * its cb @@ -65,61 +101,12 @@ struct MERCHANT_DepositConfirmation */ unsigned int exit_status; -}; - - - -/** - * Fetch the deposit fee related to the given coin aggregate. - * @param connection the connection to send an error response to - * @param coin_aggregate a coin "aggregate" is the JSON set of - * values contained in a single cell of the 'coins' array sent - * in a payment - * @param deposit_fee where to store the resulting deposit fee - * @param mint_index the index which points the chosen mint within - * the global 'mints' array - * @return GNUNET_OK if successful, GNUNET_NO if the data supplied - * is invalid (including the case when the key is not found), - * GNUNET_SYSERR upon internal errors - */ -static int -deposit_fee_from_coin_aggregate (struct MHD_Connection *connection, - json_t *coin_aggregate, - struct TALER_Amount *deposit_fee, - unsigned int mint_index) -{ - int res; - const struct TALER_MINT_Keys *keys; - const struct TALER_MINT_DenomPublicKey *denom_details; - struct TALER_DenominationPublicKey denom; + /** + * Offset of this coin into the array of all coins outcomes + */ + unsigned int index; - struct TMH_PARSE_FieldSpecification spec[] = { - TMH_PARSE_member_denomination_public_key ("denom_pub", &denom), - TMH_PARSE_MEMBER_END - }; - - res = TMH_PARSE_json_data (connection, - coin_aggregate, - spec); - if (GNUNET_OK != res) - return res; /* may return GNUNET_NO */ - - if (1 == mints[mint_index]->pending) - return GNUNET_SYSERR; - keys = TALER_MINT_get_keys (mints[mint_index]->conn); - denom_details = TALER_MINT_get_denomination_key (keys, &denom); - if (NULL == denom_details) - { - TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:o}", - "hint", "unknown denom to mint", - "denom_pub", TALER_json_from_rsa_public_key (denom.rsa_public_key)); - return GNUNET_NO; - } - *deposit_fee = denom_details->fee_deposit; - return GNUNET_OK; -} +}; /** @@ -149,131 +136,259 @@ struct PayContext void *json_parse_context; /** - * Response to return, NULL if we don't have one yet. + * Root node of the request body in JSON. */ - struct MHD_Response *response; + json_t *root; /** - * Transaction id + * Coins included in @e root. + */ + json_t *coins; + + /** + * Mint URI given in @e root. + */ + char *chosen_mint; + + /** + * Transaction ID given in @e root. */ uint64_t transaction_id; /** - * How many coins this paymen is made of. + * Maximum fee from @e root. */ - unsigned int coins_cnt; + struct TALER_Amount max_fee; /** - * HTTP status code to use for the reply, i.e 200 for "OK". - * Special value UINT_MAX is used to indicate hard errors - * (no reply, return MHD_NO). + * Amount from @e root. */ - unsigned int response_code; + struct TALER_Amount amount; -}; + /** + * Timestamp from @e root. + */ + struct GNUNET_TIME_Absolute timestamp; -/** - * Information needed by a single /deposit callback to refer to its - * own coin inside the confirmations array, namely `struct MERCHANT_DepositConfirmation *dc` - * above. Note: this information can NOT be shared between all the callbacks. - */ -struct DepositCallbackContext -{ + /** + * Refund deadline from @e root. + */ + struct GNUNET_TIME_Absolute refund_deadline; /** - * Offset of this coin into the array of all coins outcomes + * "H_contract" from @e root. */ - unsigned int index; + struct GNUNET_HashCode h_contract; /** - * Reference to the main PayContext + * */ - struct PayContext *pc; + struct GNUNET_TIME_Absolute edate; + + /** + * Response to return, NULL if we don't have one yet. + */ + struct MHD_Response *response; + + /** + * Number of coins this payment is made of. + */ + unsigned int coins_cnt; + + /** + * Number of transactions still pending. + */ + unsigned int pending; + + /** + * HTTP status code to use for the reply, i.e 200 for "OK". + * Special value UINT_MAX is used to indicate hard errors + * (no reply, return MHD_NO). + */ + unsigned int response_code; }; + /** * Callback to handle a deposit permission's response. * - * @param cls see `struct MERCHANT_DepositConfirmationCls` (i.e. a poinetr to the global - * array of confirmations and an index for this call in that array). That way, the last - * executed callback can detect that no other confirmations are on the way, and can pack - * a response for the wallet - * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; - * 0 if the mint's reply is bogus (fails to follow the protocol) - * @param proof the received JSON reply, should be kept as proof (and, in case of errors, - * be forwarded to the customer) + * @param cls a `struct MERCHANT_DepositConfirmation` (i.e. a pointer + * into the global array of confirmations and an index for this call + * in that array). That way, the last executed callback can detect + * that no other confirmations are on the way, and can pack a response + * for the wallet + * @param http_status HTTP response code, #MHD_HTTP_OK + * (200) for successful deposit; 0 if the mint's reply is bogus (fails + * to follow the protocol) + * @param proof the received JSON reply, + * should be kept as proof (and, in case of errors, be forwarded to + * the customer) */ static void deposit_cb (void *cls, unsigned int http_status, json_t *proof) { - struct DepositCallbackContext *dcc = cls; - int i; - - /*FIXME the index is the same for every individual cb */ - if (GNUNET_SYSERR == - TALER_MERCHANTDB_deposit_permission_update (db_conn, - dcc->pc->transaction_id, - 0)) - /* TODO */ - printf ("db error\n"); - dcc->pc->dc[dcc->index].ackd = 1; - dcc->pc->dc[dcc->index].exit_status = http_status; - dcc->pc->dc[dcc->index].proof = proof; - - /* loop through the confirmation array and return accordingly */ - for (i = 0; i < dcc->pc->coins_cnt; i++) - { - /* just return if there is at least one coin to be still - confirmed */ - if (! dcc->pc->dc[i].ackd) - { - printf ("still vacant coins\n"); - return; - } - } - - printf ("All /deposit(s) ack'd\n"); - - dcc->pc->response = MHD_create_response_from_buffer (strlen ("All coins ack'd by the mint\n"), - "All coins ack'd by the mint\n", - MHD_RESPMEM_MUST_COPY); - dcc->pc->response_code = MHD_HTTP_OK; - /* Clean up what we can already */ - MHD_resume_connection (dcc->pc->connection); - TM_trigger_daemon (); /* we resumed, kick MHD */ + struct MERCHANT_DepositConfirmation *dc = cls; + struct PayContext *pc = dc->pc; + + /* FIXME: store to DB! */ + dc->ackd = 1; + dc->exit_status = http_status; + dc->proof = proof; /* FIXME: needs rc+1 */ + pc->pending--; + if (0 != pc->pending) + return; /* still more to do */ + + pc->response + = MHD_create_response_from_buffer (strlen ("All coins ack'd by the mint\n"), + "All coins ack'd by the mint\n", + MHD_RESPMEM_MUST_COPY); + /* FIXME: move this logic into a function: */ + pc->response_code = MHD_HTTP_OK; + MHD_resume_connection (pc->connection); + TMH_trigger_daemon (); /* we resumed, kick MHD */ } +/** + * Custom cleanup routine for a `struct PayContext`. + * + * @param hc the `struct PayContext` to clean up. + */ static void pay_context_cleanup (struct TM_HandlerContext *hc) { - int i; struct PayContext *pc = (struct PayContext *) hc; - - for (i = 0; i < pc->coins_cnt; i++) - GNUNET_free_non_null (pc->dc[i].dcc); + unsigned int i; TMH_PARSE_post_cleanup_callback (pc->json_parse_context); + for (i=0;icoins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; - #if 0 + /* FIXME: clean up 'dc'! */ + } + GNUNET_free_non_null (pc->dc); if (NULL != pc->response) { - /* undestroyable regardless of the other MHD_destroy_response called - in this source, FIXME */ - MHD_destroy_response (pc->response); pc->response = NULL; } - #endif - - GNUNET_free_non_null (pc->dc); GNUNET_free (pc); } +/** + * Function called with the result of our mint lookup. + * + * @param cls the `struct PayContext` + * @param mint NULL if mint was not found to be acceptable + * @param mh NULL if mint was not found to be acceptable + */ +static void +process_pay_with_mint (void *cls, + struct MERCHANT_Mint *mint, + struct TALER_MINT_Handle *mh) +{ + struct PayContext *pc = cls; + struct TALER_Amount acc_fee; + struct TALER_Amount coin_fee; + const struct TALER_MINT_Keys *keys; + unsigned int i; + + if (NULL == mint) + { + /* The mint on offer is not in the set of our (trusted) + mints. Reject the payment. */ + MHD_resume_connection (pc->connection); + pc->response_code = 403; + pc->response = TMH_RESPONSE_make_external_error ("unknown mint"); + TMH_trigger_daemon (); + return; + } + + keys = TALER_MINT_get_keys (mh); + if (NULL == keys) + { + GNUNET_break (0); + pc->response_code = UINT_MAX; + pc->response = TMH_RESPONSE_make_internal_error ("no keys"); + TMH_trigger_daemon (); + return; + } + + /* FIXME: do not just total up the fees, but also + the value of the deposited coins! */ + for (i=0;icoins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + const struct TALER_MINT_DenomPublicKey *denom_details; + + denom_details = TALER_MINT_get_denomination_key (keys, + &dc->denom); + if (NULL == denom_details) + { + pc->response_code = MHD_HTTP_BAD_REQUEST; + pc->response + = TMH_RESPONSE_make_json_pack ("{s:s, s:o}", + "hint", "unknown denom to mint", + "denom_pub", TALER_json_from_rsa_public_key (dc->denom.rsa_public_key)); + TMH_trigger_daemon (); + return; + } + if (0 == i) + acc_fee = denom_details->fee_deposit; + else + TALER_amount_add (&acc_fee, + &denom_details->fee_deposit, + &coin_fee); + } + + if (-1 == TALER_amount_cmp (&pc->max_fee, + &acc_fee)) + { + pc->response_code = MHD_HTTP_NOT_ACCEPTABLE; + pc->response = TMH_RESPONSE_make_external_error ("fees too high"); + TMH_trigger_daemon (); + return; + } + + /* Initiate /deposit operation for all coins */ + for (i=0;icoins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + + dc->dh = TALER_MINT_deposit (mh, + &dc->percoin_amount, + pc->edate, + j_wire, + &pc->h_contract, + &dc->coin_pub, + &dc->ub_sig, + &dc->denom, + pc->timestamp, + pc->transaction_id, + &pubkey, + pc->refund_deadline, + &dc->coin_sig, + &deposit_cb, + dc); + if (NULL == dc->dh) + { + MHD_resume_connection (pc->connection); + pc->response_code = MHD_HTTP_SERVICE_UNAVAILABLE; + pc->response = TMH_RESPONSE_make_json_pack ("{s:s, s:i}", + "mint", pc->chosen_mint, + "transaction_id", pc->transaction_id); + TMH_trigger_daemon (); + return; + } + } +} + + /** * Accomplish this payment. * @@ -294,52 +409,9 @@ MH_handler_pay (struct TMH_RequestHandler *rh, size_t *upload_data_size) { struct PayContext *pc; - json_t *root; - json_t *coins; - char *chosen_mint; - json_t *coin_aggregate; - unsigned int mint_index; /*a cell in the global array*/ - unsigned int coins_index; - unsigned int coins_cnt; - uint64_t transaction_id; int res; - struct TALER_MINT_DepositHandle *dh; - struct TALER_Amount max_fee; - struct TALER_Amount acc_fee; - struct TALER_Amount coin_fee; - struct TALER_Amount amount; - struct TALER_Amount percoin_amount; - struct GNUNET_TIME_Absolute edate; - struct GNUNET_TIME_Absolute timestamp; - struct GNUNET_TIME_Absolute refund_deadline; - struct TALER_MerchantPublicKeyP pubkey; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_DenominationPublicKey denom_pub; - struct TALER_DenominationSignature ub_sig; - struct TALER_CoinSpendSignatureP coin_sig; - struct GNUNET_HashCode h_contract; - struct MERCHANT_DepositConfirmation *dc; - - struct TMH_PARSE_FieldSpecification spec[] = { - TMH_PARSE_member_array ("coins", &coins), - TMH_PARSE_member_string ("mint", &chosen_mint), - TMH_PARSE_member_amount ("max_fee", &max_fee), - TMH_PARSE_member_amount ("amount", &amount), - TMH_PARSE_member_time_abs ("timestamp", ×tamp), - TMH_PARSE_member_time_abs ("refund_deadline", &refund_deadline), - TMH_PARSE_member_uint64 ("transaction_id", &transaction_id), - TMH_PARSE_member_fixed ("H_contract", &h_contract), - TMH_PARSE_MEMBER_END - }; - - struct TMH_PARSE_FieldSpecification coin_aggregate_spec[] = { - TMH_PARSE_member_amount ("f", &percoin_amount), - TMH_PARSE_member_fixed ("coin_pub", &coin_pub.eddsa_pub), - TMH_PARSE_member_denomination_public_key ("denom_pub", &denom_pub), - TMH_PARSE_member_denomination_signature ("ub_sig", &ub_sig), - TMH_PARSE_member_fixed ("coin_sig", &coin_sig.eddsa_signature), - TMH_PARSE_MEMBER_END - }; + json_t *coin; + unsigned int coins_index; if (NULL == *connection_cls) { @@ -355,21 +427,17 @@ MH_handler_pay (struct TMH_RequestHandler *rh, } if (0 != pc->response_code) { + /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == pc->response_code) return MHD_NO; /* hard error */ - /* We are *done* processing the request, just queue the response (!) */ res = MHD_queue_response (connection, pc->response_code, pc->response); - #if 0 - if (pc->response != NULL) + if (NULL != pc->response) { - /* undestroyable regardless of the other MHD_destroy_response called - in this source, FIXME */ MHD_destroy_response (pc->response); pc->response = NULL; } - #endif return res; } @@ -377,202 +445,81 @@ MH_handler_pay (struct TMH_RequestHandler *rh, &pc->json_parse_context, upload_data, upload_data_size, - &root); + &pc->root); if (GNUNET_SYSERR == res) - return MHD_NO; - /* the POST's body has to be further fetched */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - res = TMH_PARSE_json_data (connection, - root, - spec); + return MHD_NO; /* error parsing JSON */ + if ((GNUNET_NO == res) || (NULL == pc->root)) + return MHD_YES; /* the POST's body has to be further fetched */ - if (GNUNET_YES != res) - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - - /* 1 Check if the chosen mint is among the merchant's preferred. - - An error in this case could be due to: - - * the wallet indicated a non existent mint - * the wallet indicated a non trusted mint - - NOTE: by preventively checking this, the merchant - avoids getting HTTP response codes from random - websites that may mislead the wallet in the way - of managing the error. Of course, that protect the - merchant from POSTing coins to untrusted mints. - - */ + /* Got the JSON upload, parse it (FIXME: all of this here?) */ - for (mint_index = 0; mint_index <= nmints; mint_index++) + /* We got no 'edate' from frontend. Generate it here; it will be + timestamp plus the edate_delay supplied in config file */ + if (NULL == json_object_get (pc->root, "edate")) { - /* no mint found in array */ - if (mint_index == nmints) - { - mint_index = -1; - break; - } - - /* test it by checking public key */ - if (0 == strcmp (mints[mint_index]->hostname, - chosen_mint)) - break; - - } - - if (-1 == mint_index) - return TMH_RESPONSE_reply_external_error (connection, "unknown mint"); - - /* no 'edate' from frontend. Generate it here; it will be timestamp - + a edate delay supplied in config file */ - if (NULL == json_object_get (root, "edate")) - { - edate = GNUNET_TIME_absolute_add (timestamp, edate_delay); - if (-1 == json_object_set (root, "edate", TALER_json_from_abs (edate))) + pc->edate = GNUNET_TIME_absolute_add (pc->timestamp, // FIXME: uninit! + edate_delay); + if (-1 == json_object_set (pc->root, + "edate", + TALER_json_from_abs (pc->edate))) return MHD_NO; } - coins_cnt = json_array_size (coins); - - if (0 == coins_cnt) - return TMH_RESPONSE_reply_external_error (connection, "no coins given"); - - json_array_foreach (coins, coins_index, coin_aggregate) { - res = deposit_fee_from_coin_aggregate (connection, - coin_aggregate, - &coin_fee, - mint_index); - if (GNUNET_NO == res) - return MHD_YES; - if (GNUNET_SYSERR == res) - return MHD_NO; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_array ("coins", &pc->coins), + TMH_PARSE_member_string ("mint", &pc->chosen_mint), + TMH_PARSE_member_amount ("max_fee", &pc->max_fee), + TMH_PARSE_member_amount ("amount", &pc->amount), + TMH_PARSE_member_time_abs ("edate", &pc->edate), + TMH_PARSE_member_time_abs ("timestamp", &pc->timestamp), + TMH_PARSE_member_time_abs ("refund_deadline", &pc->refund_deadline), + TMH_PARSE_member_uint64 ("transaction_id", &pc->transaction_id), + TMH_PARSE_member_fixed ("H_contract", &pc->h_contract), + TMH_PARSE_MEMBER_END + }; - if (0 == coins_index) - acc_fee = coin_fee; - else - TALER_amount_add (&acc_fee, - &acc_fee, - &coin_fee); + res = TMH_PARSE_json_data (connection, + pc->root, + spec); + if (GNUNET_YES != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + + if (0 == json_array_size (pc->coins)) + return TMH_RESPONSE_reply_external_error (connection, + "no coins given"); } + pc->dc = GNUNET_new_array (json_array_size (pc->coins), + struct MERCHANT_DepositConfirmation); - - if (-1 == TALER_amount_cmp (&max_fee, &acc_fee)) - return MHD_HTTP_NOT_ACCEPTABLE; - - /* cutting off unneeded fields from deposit permission as - gotten from the wallet */ - if (-1 == json_object_del (root, "mint")) - return TMH_RESPONSE_reply_external_error (connection, - "malformed/non-existent 'mint' field"); - if (-1 == json_object_del (root, "coins")) - return TMH_RESPONSE_reply_external_error (connection, - "malformed/non-existent 'coins' field"); - - /* adding our public key to deposit permission */ - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pubkey.eddsa_pub); - json_object_set_new (root, - "merchant_pub", - TALER_json_from_data (&pubkey, sizeof (pubkey))); - - /* since memory is zero'd out by GNUNET_malloc, any 'ackd' field will be - (implicitly) set to false */ - dc = GNUNET_malloc (coins_cnt * sizeof (struct MERCHANT_DepositConfirmation)); - if (NULL == dc) - return TMH_RESPONSE_reply_internal_error (connection, "memory failure"); - - /* DEBUG CHECKPOINT: return a provisory fullfilment page to the wallet - to test the reception of coins array */ - - #ifdef COINSCHECKPOINT - rh->data = "Coins received\n"; - return TMH_MHD_handler_static_response (rh, - connection, - connection_cls, - upload_data, - upload_data_size); - - #endif - - /* suspend connection until the last coin has been ack'd to the cb. - That last cb will finally resume the connection and send back a response */ - MHD_suspend_connection (connection); - - pc->dc = dc; - pc->coins_cnt = coins_cnt; - pc->transaction_id = transaction_id; - - - json_array_foreach (coins, coins_index, coin_aggregate) + json_array_foreach (pc->coins, coins_index, coin) { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[coins_index]; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_denomination_public_key ("denom_pub", &dc->denom), + TMH_PARSE_member_amount ("f", &dc->percoin_amount), + TMH_PARSE_member_fixed ("coin_pub", &dc->coin_pub), + TMH_PARSE_member_denomination_signature ("ub_sig", &dc->ub_sig), + TMH_PARSE_member_fixed ("coin_sig", &dc->coin_sig), + TMH_PARSE_MEMBER_END + }; - /* 3 For each coin in DB - - a. Generate a deposit permission - b. store it in DB - c. POST to the mint (see mint-lib for this) - (retry until getting a persisten state) - */ - - /* a */ - if (-1 == json_object_update (root, coin_aggregate)) - return TMH_RESPONSE_reply_internal_error (connection, - "deposit permission not generated for storing"); - - /* b */ - char *deposit_permission_str = json_dumps (root, JSON_COMPACT); - if (GNUNET_OK != TALER_MERCHANTDB_deposit_permission_store (db_conn, - deposit_permission_str, - transaction_id, - 1, - mints[mint_index]->hostname)) - return TMH_RESPONSE_reply_internal_error (connection, "internal DB failure"); res = TMH_PARSE_json_data (connection, - coin_aggregate, - coin_aggregate_spec); - if (GNUNET_OK != res) - return res; /* may return GNUNET_NO */ - - /* c */ - struct DepositCallbackContext *percoin_dcc = GNUNET_new (struct DepositCallbackContext); - pc->dc[coins_index].dcc = percoin_dcc; - percoin_dcc->index = coins_index; - percoin_dcc->pc = pc; - - dh = TALER_MINT_deposit (mints[mint_index]->conn, - &percoin_amount, - edate, - j_wire, - &h_contract, - &coin_pub, - &ub_sig, - &denom_pub, - timestamp, - transaction_id, - &pubkey, - refund_deadline, - &coin_sig, - &deposit_cb, - percoin_dcc); /*FIXME TODO instantiate an individual cls for each - cb: each of them needs an index which points the - array of all the confirmations */ - if (NULL == dh) - { - MHD_resume_connection (connection); - return TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_SERVICE_UNAVAILABLE, - "{s:s, s:i}", - "mint", mints[mint_index]->hostname, - "transaction_id", transaction_id); - } + coin, + spec); + if (GNUNET_YES != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + + } - GNUNET_SCHEDULER_cancel (mints[mint_index]->poller_task); - GNUNET_SCHEDULER_add_now (context_task, mints[mint_index]->ctx); + /* Find the responsible mint, this may take a while... */ + TMH_MINTS_find_mint (pc->chosen_mint, + &process_pay_with_mint, + pc); + /* Suspend connection until the last coin has been ack'd or + until we have encountered a hard error. + Eventually, we will resume the connection and send back a response. */ + MHD_suspend_connection (connection); return MHD_YES; - - /* 4 Return response code: success, or whatever data the - mint sent back regarding some bad coin */ } diff --git a/src/backend/taler-merchant-httpd_responses.c b/src/backend/taler-merchant-httpd_responses.c index 1e2902c2..d8ba1170 100644 --- a/src/backend/taler-merchant-httpd_responses.c +++ b/src/backend/taler-merchant-httpd_responses.c @@ -29,21 +29,16 @@ /** - * Send JSON object as response. + * Make JSON response object. * - * @param connection the MHD connection * @param json the json object - * @param response_code the http response code - * @return MHD result code + * @return MHD response object */ -int -TMH_RESPONSE_reply_json (struct MHD_Connection *connection, - const json_t *json, - unsigned int response_code) +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json) { struct MHD_Response *resp; char *json_str; - int ret; json_str = json_dumps (json, JSON_INDENT(2)); GNUNET_assert (NULL != json_str); @@ -53,11 +48,34 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, { free (json_str); GNUNET_break (0); - return MHD_NO; + return NULL; } (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); + return resp; +} + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *resp; + int ret; + + resp = TMH_RESPONSE_make_json (json); + if (NULL == resp) + return MHD_NO; ret = MHD_queue_response (connection, response_code, resp); @@ -66,6 +84,40 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, } +/** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...) +{ + json_t *json; + va_list argp; + struct MHD_Response *ret; + json_error_t jerror; + + 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; + } + ret = TMH_RESPONSE_make_json (json); + json_decref (json); + return ret; +} + + /** * Function to call to handle the request by building a JSON * reply from a format string and varargs. @@ -106,6 +158,22 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, return ret; } + +/** + * Create a response indicating an internal error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_internal_error (const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "internal error", + "hint", hint); +} + + /** * Send a response indicating an internal error. * @@ -124,6 +192,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, "hint", hint); } + /** * Send a response indicating that the request was too big. * @@ -186,6 +255,7 @@ TMH_RESPONSE_add_global_headers (struct MHD_Response *response) "close"); } + /** * Send a response indicating an external error. * @@ -203,4 +273,21 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, "error", "client error", "hint", hint); } + + +/** + * Create a response indicating an external error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_external_error (const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "client error", + "hint", hint); +} + + /* end of taler-mint-httpd_responses.c */ diff --git a/src/backend/taler-merchant-httpd_responses.h b/src/backend/taler-merchant-httpd_responses.h index ad063eb8..7240d601 100644 --- a/src/backend/taler-merchant-httpd_responses.h +++ b/src/backend/taler-merchant-httpd_responses.h @@ -30,6 +30,16 @@ #include #include +/** + * Make JSON response object. + * + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json); + + /** * Send JSON object as response. * @@ -44,6 +54,18 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, unsigned int response_code); +/** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...); + + /** * Function to call to handle the request by building a JSON * reply from a format string and varargs. @@ -70,6 +92,7 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, int TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); + /** * Send a response indicating an internal error. * @@ -80,6 +103,18 @@ TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); int TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, const char *hint); + + +/** + * Create a response indicating an internal error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_internal_error (const char *hint); + + /** * Send a response indicating an external error. * @@ -90,6 +125,18 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, const char *hint); + + +/** + * Create a response indicating an external error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_external_error (const char *hint); + + /** * Send a response indicating that the request was too big. * @@ -99,6 +146,7 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); + /** * Add headers we want to return in every response. * Useful for testing, like if we want to always close -- cgit v1.2.3