/* This file is part of TALER Copyright (C) 2014-2023 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 */ /** * @file taler-exchange-httpd_reserves_history.c * @brief Handle /reserves/$RESERVE_PUB HISTORY requests * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff */ #include "platform.h" #include #include #include "taler_json_lib.h" #include "taler_dbevents.h" #include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_reserves_history.h" #include "taler-exchange-httpd_responses.h" /** * Compile the history of a reserve into a JSON object. * * @param rh reserve history to JSON-ify * @return json representation of the @a rh, NULL on error */ static json_t * compile_reserve_history ( const struct TALER_EXCHANGEDB_ReserveHistory *rh) { json_t *json_history; json_history = json_array (); GNUNET_assert (NULL != json_history); for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh; NULL != pos; pos = pos->next) { switch (pos->type) { case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE: { const struct TALER_EXCHANGEDB_BankTransfer *bank = pos->details.bank; if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "CREDIT"), GNUNET_JSON_pack_timestamp ("timestamp", bank->execution_date), GNUNET_JSON_pack_string ("sender_account_url", bank->sender_account_details), GNUNET_JSON_pack_uint64 ("wire_reference", bank->wire_reference), TALER_JSON_pack_amount ("amount", &bank->amount)))) { GNUNET_break (0); json_decref (json_history); return NULL; } break; } case TALER_EXCHANGEDB_RO_WITHDRAW_COIN: { const struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw = pos->details.withdraw; if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "WITHDRAW"), GNUNET_JSON_pack_data_auto ("reserve_sig", &withdraw->reserve_sig), GNUNET_JSON_pack_data_auto ("h_coin_envelope", &withdraw->h_coin_envelope), GNUNET_JSON_pack_data_auto ("h_denom_pub", &withdraw->denom_pub_hash), TALER_JSON_pack_amount ("withdraw_fee", &withdraw->withdraw_fee), TALER_JSON_pack_amount ("amount", &withdraw->amount_with_fee)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; case TALER_EXCHANGEDB_RO_RECOUP_COIN: { const struct TALER_EXCHANGEDB_Recoup *recoup = pos->details.recoup; struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; if (TALER_EC_NONE != TALER_exchange_online_confirm_recoup_sign ( &TEH_keys_exchange_sign_, recoup->timestamp, &recoup->value, &recoup->coin.coin_pub, &recoup->reserve_pub, &pub, &sig)) { GNUNET_break (0); json_decref (json_history); return NULL; } if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "RECOUP"), GNUNET_JSON_pack_data_auto ("exchange_pub", &pub), GNUNET_JSON_pack_data_auto ("exchange_sig", &sig), GNUNET_JSON_pack_timestamp ("timestamp", recoup->timestamp), TALER_JSON_pack_amount ("amount", &recoup->value), GNUNET_JSON_pack_data_auto ("coin_pub", &recoup->coin.coin_pub)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK: { const struct TALER_EXCHANGEDB_ClosingTransfer *closing = pos->details.closing; struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; if (TALER_EC_NONE != TALER_exchange_online_reserve_closed_sign ( &TEH_keys_exchange_sign_, closing->execution_date, &closing->amount, &closing->closing_fee, closing->receiver_account_details, &closing->wtid, &pos->details.closing->reserve_pub, &pub, &sig)) { GNUNET_break (0); json_decref (json_history); return NULL; } if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "CLOSING"), GNUNET_JSON_pack_string ("receiver_account_details", closing->receiver_account_details), GNUNET_JSON_pack_data_auto ("wtid", &closing->wtid), GNUNET_JSON_pack_data_auto ("exchange_pub", &pub), GNUNET_JSON_pack_data_auto ("exchange_sig", &sig), GNUNET_JSON_pack_timestamp ("timestamp", closing->execution_date), TALER_JSON_pack_amount ("amount", &closing->amount), TALER_JSON_pack_amount ("closing_fee", &closing->closing_fee)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; case TALER_EXCHANGEDB_RO_PURSE_MERGE: { const struct TALER_EXCHANGEDB_PurseMerge *merge = pos->details.merge; if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "MERGE"), GNUNET_JSON_pack_data_auto ("h_contract_terms", &merge->h_contract_terms), GNUNET_JSON_pack_data_auto ("merge_pub", &merge->merge_pub), GNUNET_JSON_pack_uint64 ("min_age", merge->min_age), GNUNET_JSON_pack_uint64 ("flags", merge->flags), GNUNET_JSON_pack_data_auto ("purse_pub", &merge->purse_pub), GNUNET_JSON_pack_data_auto ("reserve_sig", &merge->reserve_sig), GNUNET_JSON_pack_timestamp ("merge_timestamp", merge->merge_timestamp), GNUNET_JSON_pack_timestamp ("purse_expiration", merge->purse_expiration), TALER_JSON_pack_amount ("purse_fee", &merge->purse_fee), TALER_JSON_pack_amount ("amount", &merge->amount_with_fee), GNUNET_JSON_pack_bool ("merged", merge->merged)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; case TALER_EXCHANGEDB_RO_HISTORY_REQUEST: { const struct TALER_EXCHANGEDB_HistoryRequest *history = pos->details.history; if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "HISTORY"), GNUNET_JSON_pack_data_auto ("reserve_sig", &history->reserve_sig), GNUNET_JSON_pack_timestamp ("request_timestamp", history->request_timestamp), TALER_JSON_pack_amount ("amount", &history->history_fee)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; case TALER_EXCHANGEDB_RO_OPEN_REQUEST: { const struct TALER_EXCHANGEDB_OpenRequest *orq = pos->details.open_request; if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "OPEN"), GNUNET_JSON_pack_uint64 ("requested_min_purses", orq->purse_limit), GNUNET_JSON_pack_data_auto ("reserve_sig", &orq->reserve_sig), GNUNET_JSON_pack_timestamp ("request_timestamp", orq->request_timestamp), GNUNET_JSON_pack_timestamp ("requested_expiration", orq->reserve_expiration), TALER_JSON_pack_amount ("open_fee", &orq->open_fee)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; case TALER_EXCHANGEDB_RO_CLOSE_REQUEST: { const struct TALER_EXCHANGEDB_CloseRequest *crq = pos->details.close_request; if (0 != json_array_append_new ( json_history, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "CLOSE"), GNUNET_JSON_pack_data_auto ("reserve_sig", &crq->reserve_sig), GNUNET_is_zero (&crq->target_account_h_payto) ? GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("h_payto", NULL)) : GNUNET_JSON_pack_data_auto ("h_payto", &crq->target_account_h_payto), GNUNET_JSON_pack_timestamp ("request_timestamp", crq->request_timestamp)))) { GNUNET_break (0); json_decref (json_history); return NULL; } } break; } } return json_history; } /** * Add the headers we want to set for every /keys response. * * @param cls the key state to use * @param[in,out] response the response to modify */ static void add_response_headers (void *cls, struct MHD_Response *response) { (void) cls; TALER_MHD_add_global_headers (response); GNUNET_break (MHD_YES == MHD_add_response_header (response, MHD_HTTP_HEADER_CACHE_CONTROL, "no-cache")); } MHD_RESULT TEH_handler_reserves_history ( struct TEH_RequestContext *rc, const struct TALER_ReservePublicKeyP *reserve_pub) { struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL; uint64_t start_off = 0; struct TALER_Amount balance; uint64_t etag_in; uint64_t etag_out; char etagp[24]; struct MHD_Response *resp; unsigned int http_status; TALER_MHD_parse_request_number (rc->connection, "start", &start_off); { struct TALER_ReserveSignatureP reserve_sig; bool required = true; TALER_MHD_parse_request_header_auto (rc->connection, TALER_RESERVE_HISTORY_SIGNATURE_HEADER, &reserve_sig, required); if (GNUNET_OK != TALER_wallet_reserve_history_verify (start_off, reserve_pub, &reserve_sig)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE, NULL); } } /* Get etag */ { const char *etags; etags = MHD_lookup_connection_value (rc->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH); if (NULL != etags) { char dummy; unsigned long long ev; if (1 != sscanf (etags, "\"%llu\"%c", &ev, &dummy)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Client send malformed `If-None-Match' header `%s'\n", etags); etag_in = 0; } else { etag_in = (uint64_t) ev; } } else { etag_in = start_off; } } { enum GNUNET_DB_QueryStatus qs; qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, reserve_pub, start_off, etag_in, &etag_out, &balance, &rh); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "get_reserve_history"); case GNUNET_DB_STATUS_SOFT_ERROR: return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, "get_reserve_history"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* Handled below */ break; } } GNUNET_snprintf (etagp, sizeof (etagp), "\"%llu\"", (unsigned long long) etag_out); if (etag_in == etag_out) { return TEH_RESPONSE_reply_not_modified (rc->connection, etagp, &add_response_headers, NULL); } if (NULL == rh) { /* 204: empty history */ resp = MHD_create_response_from_buffer (0, "", MHD_RESPMEM_PERSISTENT); http_status = MHD_HTTP_NO_CONTENT; } else { json_t *history; http_status = MHD_HTTP_OK; history = compile_reserve_history (rh); TEH_plugin->free_reserve_history (TEH_plugin->cls, rh); rh = NULL; if (NULL == history) { GNUNET_break (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, NULL); } resp = TALER_MHD_MAKE_JSON_PACK ( TALER_JSON_pack_amount ("balance", &balance), GNUNET_JSON_pack_array_steal ("history", history)); } add_response_headers (NULL, resp); GNUNET_break (MHD_YES == MHD_add_response_header (resp, MHD_HTTP_HEADER_ETAG, etagp)); { MHD_RESULT ret; ret = MHD_queue_response (rc->connection, http_status, resp); GNUNET_break (MHD_YES == ret); MHD_destroy_response (resp); return ret; } } /* end of taler-exchange-httpd_reserves_history.c */