taler-exchange-httpd_coins_get.c (28522B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_coins_get.c 18 * @brief Handle GET /coins/$COIN_PUB/history requests 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <jansson.h> 24 #include "taler/taler_mhd_lib.h" 25 #include "taler/taler_json_lib.h" 26 #include "taler/taler_dbevents.h" 27 #include "taler-exchange-httpd_keys.h" 28 #include "taler-exchange-httpd_coins_get.h" 29 #include "taler-exchange-httpd_responses.h" 30 31 32 /** 33 * Add the headers we want to set for every response. 34 * 35 * @param cls the key state to use 36 * @param[in,out] response the response to modify 37 */ 38 static void 39 add_response_headers (void *cls, 40 struct MHD_Response *response) 41 { 42 (void) cls; 43 GNUNET_break (MHD_YES == 44 MHD_add_response_header (response, 45 MHD_HTTP_HEADER_CACHE_CONTROL, 46 "no-cache")); 47 } 48 49 50 /** 51 * Compile the transaction history of a coin into a JSON object. 52 * 53 * @param coin_pub public key of the coin 54 * @param tl transaction history to JSON-ify 55 * @return json representation of the @a rh, NULL on error 56 */ 57 static json_t * 58 compile_transaction_history ( 59 const struct TALER_CoinSpendPublicKeyP *coin_pub, 60 const struct TALER_EXCHANGEDB_TransactionList *tl) 61 { 62 json_t *history; 63 64 history = json_array (); 65 if (NULL == history) 66 { 67 GNUNET_break (0); /* out of memory!? */ 68 return NULL; 69 } 70 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 71 NULL != pos; 72 pos = pos->next) 73 { 74 switch (pos->type) 75 { 76 case TALER_EXCHANGEDB_TT_DEPOSIT: 77 { 78 const struct TALER_EXCHANGEDB_DepositListEntry *deposit = 79 pos->details.deposit; 80 struct TALER_MerchantWireHashP h_wire; 81 82 TALER_merchant_wire_signature_hash (deposit->receiver_wire_account, 83 &deposit->wire_salt, 84 &h_wire); 85 #if ENABLE_SANITY_CHECKS 86 /* internal sanity check before we hand out a bogus sig... */ 87 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 88 if (GNUNET_OK != 89 TALER_wallet_deposit_verify ( 90 &deposit->amount_with_fee, 91 &deposit->deposit_fee, 92 &h_wire, 93 &deposit->h_contract_terms, 94 deposit->no_wallet_data_hash 95 ? NULL 96 : &deposit->wallet_data_hash, 97 deposit->no_age_commitment 98 ? NULL 99 : &deposit->h_age_commitment, 100 &deposit->h_policy, 101 &deposit->h_denom_pub, 102 deposit->timestamp, 103 &deposit->merchant_pub, 104 deposit->refund_deadline, 105 coin_pub, 106 &deposit->csig)) 107 { 108 GNUNET_break (0); 109 json_decref (history); 110 return NULL; 111 } 112 #endif 113 if (0 != 114 json_array_append_new ( 115 history, 116 GNUNET_JSON_PACK ( 117 GNUNET_JSON_pack_string ("type", 118 "DEPOSIT"), 119 GNUNET_JSON_pack_uint64 ("history_offset", 120 pos->coin_history_id), 121 TALER_JSON_pack_amount ("amount", 122 &deposit->amount_with_fee), 123 TALER_JSON_pack_amount ("deposit_fee", 124 &deposit->deposit_fee), 125 GNUNET_JSON_pack_data_auto ("merchant_pub", 126 &deposit->merchant_pub), 127 GNUNET_JSON_pack_timestamp ("timestamp", 128 deposit->timestamp), 129 GNUNET_JSON_pack_allow_null ( 130 GNUNET_JSON_pack_timestamp ("refund_deadline", 131 deposit->refund_deadline)), 132 GNUNET_JSON_pack_data_auto ("h_contract_terms", 133 &deposit->h_contract_terms), 134 GNUNET_JSON_pack_data_auto ("h_wire", 135 &h_wire), 136 GNUNET_JSON_pack_data_auto ("h_denom_pub", 137 &deposit->h_denom_pub), 138 GNUNET_JSON_pack_allow_null ( 139 deposit->has_policy 140 ? GNUNET_JSON_pack_data_auto ("h_policy", 141 &deposit->h_policy) 142 : GNUNET_JSON_pack_string ( 143 "h_policy", 144 NULL)), 145 GNUNET_JSON_pack_allow_null ( 146 deposit->no_wallet_data_hash 147 ? GNUNET_JSON_pack_string ( 148 "wallet_data_hash", 149 NULL) 150 : GNUNET_JSON_pack_data_auto ("wallet_data_hash", 151 &deposit->wallet_data_hash)), 152 GNUNET_JSON_pack_allow_null ( 153 deposit->no_age_commitment 154 ? GNUNET_JSON_pack_string ( 155 "h_age_commitment", 156 NULL) 157 : GNUNET_JSON_pack_data_auto ("h_age_commitment", 158 &deposit->h_age_commitment)), 159 GNUNET_JSON_pack_data_auto ("coin_sig", 160 &deposit->csig)))) 161 { 162 GNUNET_break (0); 163 json_decref (history); 164 return NULL; 165 } 166 break; 167 } 168 case TALER_EXCHANGEDB_TT_MELT: 169 { 170 const struct TALER_EXCHANGEDB_MeltListEntry *melt = 171 pos->details.melt; 172 const struct TALER_AgeCommitmentHashP *phac; 173 const struct TALER_BlindingMasterSeedP *pbs; 174 175 #if ENABLE_SANITY_CHECKS 176 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 177 if (GNUNET_OK != 178 TALER_wallet_melt_verify ( 179 &melt->amount_with_fee, 180 &melt->melt_fee, 181 &melt->rc, 182 &melt->h_denom_pub, 183 &melt->h_age_commitment, 184 coin_pub, 185 &melt->coin_sig)) 186 { 187 GNUNET_break (0); 188 json_decref (history); 189 return NULL; 190 } 191 #endif 192 phac = (melt->no_age_commitment) 193 ? NULL 194 : &melt->h_age_commitment; 195 pbs = melt->no_blinding_seed 196 ? NULL 197 : &melt->blinding_seed; 198 if (0 != 199 json_array_append_new ( 200 history, 201 GNUNET_JSON_PACK ( 202 GNUNET_JSON_pack_string ("type", 203 "MELT"), 204 GNUNET_JSON_pack_uint64 ("history_offset", 205 pos->coin_history_id), 206 TALER_JSON_pack_amount ("amount", 207 &melt->amount_with_fee), 208 TALER_JSON_pack_amount ("melt_fee", 209 &melt->melt_fee), 210 GNUNET_JSON_pack_data_auto ("rc", 211 &melt->rc), 212 GNUNET_JSON_pack_data_auto ("h_denom_pub", 213 &melt->h_denom_pub), 214 GNUNET_JSON_pack_data_auto ("refresh_seed", 215 &melt->refresh_seed), 216 GNUNET_JSON_pack_allow_null ( 217 GNUNET_JSON_pack_data_auto ("blinding_seed", 218 pbs)), 219 GNUNET_JSON_pack_allow_null ( 220 GNUNET_JSON_pack_data_auto ("h_age_commitment", 221 phac)), 222 GNUNET_JSON_pack_data_auto ("coin_sig", 223 &melt->coin_sig)))) 224 { 225 GNUNET_break (0); 226 json_decref (history); 227 return NULL; 228 } 229 } 230 break; 231 case TALER_EXCHANGEDB_TT_REFUND: 232 { 233 const struct TALER_EXCHANGEDB_RefundListEntry *refund = 234 pos->details.refund; 235 struct TALER_Amount value; 236 237 #if ENABLE_SANITY_CHECKS 238 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 239 if (GNUNET_OK != 240 TALER_merchant_refund_verify ( 241 coin_pub, 242 &refund->h_contract_terms, 243 refund->rtransaction_id, 244 &refund->refund_amount, 245 &refund->merchant_pub, 246 &refund->merchant_sig)) 247 { 248 GNUNET_break (0); 249 json_decref (history); 250 return NULL; 251 } 252 #endif 253 if (0 > 254 TALER_amount_subtract (&value, 255 &refund->refund_amount, 256 &refund->refund_fee)) 257 { 258 GNUNET_break (0); 259 json_decref (history); 260 return NULL; 261 } 262 if (0 != 263 json_array_append_new ( 264 history, 265 GNUNET_JSON_PACK ( 266 GNUNET_JSON_pack_string ("type", 267 "REFUND"), 268 GNUNET_JSON_pack_uint64 ("history_offset", 269 pos->coin_history_id), 270 TALER_JSON_pack_amount ("amount", 271 &value), 272 TALER_JSON_pack_amount ("refund_fee", 273 &refund->refund_fee), 274 GNUNET_JSON_pack_data_auto ("h_contract_terms", 275 &refund->h_contract_terms), 276 GNUNET_JSON_pack_data_auto ("merchant_pub", 277 &refund->merchant_pub), 278 GNUNET_JSON_pack_uint64 ("rtransaction_id", 279 refund->rtransaction_id), 280 GNUNET_JSON_pack_data_auto ("merchant_sig", 281 &refund->merchant_sig)))) 282 { 283 GNUNET_break (0); 284 json_decref (history); 285 return NULL; 286 } 287 } 288 break; 289 case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER: 290 { 291 struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr = 292 pos->details.old_coin_recoup; 293 struct TALER_ExchangePublicKeyP epub; 294 struct TALER_ExchangeSignatureP esig; 295 296 if (TALER_EC_NONE != 297 TALER_exchange_online_confirm_recoup_refresh_sign ( 298 &TEH_keys_exchange_sign_, 299 pr->timestamp, 300 &pr->value, 301 &pr->coin.coin_pub, 302 &pr->old_coin_pub, 303 &epub, 304 &esig)) 305 { 306 GNUNET_break (0); 307 json_decref (history); 308 return NULL; 309 } 310 /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and 311 the denomination key's RSA signature over coin_pub, but as the 312 wallet should really already have this information (and cannot 313 check or do anything with it anyway if it doesn't), it seems 314 strictly unnecessary. */ 315 if (0 != 316 json_array_append_new ( 317 history, 318 GNUNET_JSON_PACK ( 319 GNUNET_JSON_pack_string ("type", 320 "RECOUP-REFRESH-RECEIVER"), 321 GNUNET_JSON_pack_uint64 ("history_offset", 322 pos->coin_history_id), 323 TALER_JSON_pack_amount ("amount", 324 &pr->value), 325 GNUNET_JSON_pack_data_auto ("exchange_sig", 326 &esig), 327 GNUNET_JSON_pack_data_auto ("exchange_pub", 328 &epub), 329 GNUNET_JSON_pack_data_auto ("coin_pub", 330 &pr->coin.coin_pub), 331 GNUNET_JSON_pack_timestamp ("timestamp", 332 pr->timestamp)))) 333 { 334 GNUNET_break (0); 335 json_decref (history); 336 return NULL; 337 } 338 break; 339 } 340 case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW: 341 { 342 const struct TALER_EXCHANGEDB_RecoupListEntry *recoup = 343 pos->details.recoup; 344 struct TALER_ExchangePublicKeyP epub; 345 struct TALER_ExchangeSignatureP esig; 346 347 if (TALER_EC_NONE != 348 TALER_exchange_online_confirm_recoup_sign ( 349 &TEH_keys_exchange_sign_, 350 recoup->timestamp, 351 &recoup->value, 352 coin_pub, 353 &recoup->reserve_pub, 354 &epub, 355 &esig)) 356 { 357 GNUNET_break (0); 358 json_decref (history); 359 return NULL; 360 } 361 if (0 != 362 json_array_append_new ( 363 history, 364 GNUNET_JSON_PACK ( 365 GNUNET_JSON_pack_string ("type", 366 "RECOUP-WITHDRAW"), 367 GNUNET_JSON_pack_uint64 ("history_offset", 368 pos->coin_history_id), 369 TALER_JSON_pack_amount ("amount", 370 &recoup->value), 371 GNUNET_JSON_pack_data_auto ("exchange_sig", 372 &esig), 373 GNUNET_JSON_pack_data_auto ("exchange_pub", 374 &epub), 375 GNUNET_JSON_pack_data_auto ("reserve_pub", 376 &recoup->reserve_pub), 377 GNUNET_JSON_pack_data_auto ("coin_sig", 378 &recoup->coin_sig), 379 GNUNET_JSON_pack_data_auto ("h_denom_pub", 380 &recoup->h_denom_pub), 381 GNUNET_JSON_pack_data_auto ("coin_blind", 382 &recoup->coin_blind), 383 // FIXME-9828: spec says we should have h_commitment? 384 // FIXME-9828: spec says we should have coin_index? 385 GNUNET_JSON_pack_data_auto ("reserve_pub", 386 &recoup->reserve_pub), 387 GNUNET_JSON_pack_timestamp ("timestamp", 388 recoup->timestamp)))) 389 { 390 GNUNET_break (0); 391 json_decref (history); 392 return NULL; 393 } 394 } 395 break; 396 case TALER_EXCHANGEDB_TT_RECOUP_REFRESH: 397 { 398 struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr = 399 pos->details.recoup_refresh; 400 struct TALER_ExchangePublicKeyP epub; 401 struct TALER_ExchangeSignatureP esig; 402 403 if (TALER_EC_NONE != 404 TALER_exchange_online_confirm_recoup_refresh_sign ( 405 &TEH_keys_exchange_sign_, 406 pr->timestamp, 407 &pr->value, 408 coin_pub, 409 &pr->old_coin_pub, 410 &epub, 411 &esig)) 412 { 413 GNUNET_break (0); 414 json_decref (history); 415 return NULL; 416 } 417 /* NOTE: we could also provide coin_pub's coin_sig, denomination key 418 hash and the denomination key's RSA signature over coin_pub, but as 419 the wallet should really already have this information (and cannot 420 check or do anything with it anyway if it doesn't), it seems 421 strictly unnecessary. */ 422 if (0 != 423 json_array_append_new ( 424 history, 425 GNUNET_JSON_PACK ( 426 GNUNET_JSON_pack_string ("type", 427 "RECOUP-REFRESH"), 428 GNUNET_JSON_pack_uint64 ("history_offset", 429 pos->coin_history_id), 430 TALER_JSON_pack_amount ("amount", 431 &pr->value), 432 GNUNET_JSON_pack_data_auto ("exchange_sig", 433 &esig), 434 GNUNET_JSON_pack_data_auto ("exchange_pub", 435 &epub), 436 GNUNET_JSON_pack_data_auto ("old_coin_pub", 437 &pr->old_coin_pub), 438 GNUNET_JSON_pack_data_auto ("coin_sig", 439 &pr->coin_sig), 440 // FIXME-#9828: spec says to return h_denom_pub 441 GNUNET_JSON_pack_data_auto ("coin_blind", 442 &pr->coin_blind), 443 // FIXME-#9828: spec says to return h_commitment 444 // FIXME-#9828: spec says to return coin_index 445 // FIXME-#9828: spec says to return new_coin_blinding_secret 446 // FIXME-#9828: spec says to return new_coin_ev 447 GNUNET_JSON_pack_timestamp ("timestamp", 448 pr->timestamp)))) 449 { 450 GNUNET_break (0); 451 json_decref (history); 452 return NULL; 453 } 454 break; 455 } 456 457 case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT: 458 { 459 struct TALER_EXCHANGEDB_PurseDepositListEntry *pd 460 = pos->details.purse_deposit; 461 const struct TALER_AgeCommitmentHashP *phac = NULL; 462 463 if (! pd->no_age_commitment) 464 phac = &pd->h_age_commitment; 465 if (0 != 466 json_array_append_new ( 467 history, 468 GNUNET_JSON_PACK ( 469 GNUNET_JSON_pack_string ("type", 470 "PURSE-DEPOSIT"), 471 GNUNET_JSON_pack_uint64 ("history_offset", 472 pos->coin_history_id), 473 TALER_JSON_pack_amount ("amount", 474 &pd->amount), 475 GNUNET_JSON_pack_string ("exchange_base_url", 476 NULL == pd->exchange_base_url 477 ? TEH_base_url 478 : pd->exchange_base_url), 479 GNUNET_JSON_pack_allow_null ( 480 GNUNET_JSON_pack_data_auto ("h_age_commitment", 481 phac)), 482 TALER_JSON_pack_amount ("deposit_fee", 483 &pd->deposit_fee), 484 GNUNET_JSON_pack_data_auto ("purse_pub", 485 &pd->purse_pub), 486 GNUNET_JSON_pack_bool ("refunded", 487 pd->refunded), 488 GNUNET_JSON_pack_data_auto ("coin_sig", 489 &pd->coin_sig), 490 GNUNET_JSON_pack_data_auto ("h_denom_pub", 491 &pd->h_denom_pub)))) 492 { 493 GNUNET_break (0); 494 json_decref (history); 495 return NULL; 496 } 497 break; 498 } 499 500 case TALER_EXCHANGEDB_TT_PURSE_REFUND: 501 { 502 const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund = 503 pos->details.purse_refund; 504 struct TALER_Amount value; 505 enum TALER_ErrorCode ec; 506 struct TALER_ExchangePublicKeyP epub; 507 struct TALER_ExchangeSignatureP esig; 508 509 if (0 > 510 TALER_amount_subtract (&value, 511 &prefund->refund_amount, 512 &prefund->refund_fee)) 513 { 514 GNUNET_break (0); 515 json_decref (history); 516 return NULL; 517 } 518 ec = TALER_exchange_online_purse_refund_sign ( 519 &TEH_keys_exchange_sign_, 520 &value, 521 &prefund->refund_fee, 522 coin_pub, 523 &prefund->purse_pub, 524 &epub, 525 &esig); 526 if (TALER_EC_NONE != ec) 527 { 528 GNUNET_break (0); 529 json_decref (history); 530 return NULL; 531 } 532 if (0 != 533 json_array_append_new ( 534 history, 535 GNUNET_JSON_PACK ( 536 GNUNET_JSON_pack_string ("type", 537 "PURSE-REFUND"), 538 GNUNET_JSON_pack_uint64 ("history_offset", 539 pos->coin_history_id), 540 TALER_JSON_pack_amount ("amount", 541 &value), 542 TALER_JSON_pack_amount ("refund_fee", 543 &prefund->refund_fee), 544 GNUNET_JSON_pack_data_auto ("exchange_sig", 545 &esig), 546 GNUNET_JSON_pack_data_auto ("exchange_pub", 547 &epub), 548 GNUNET_JSON_pack_data_auto ("purse_pub", 549 &prefund->purse_pub)))) 550 { 551 GNUNET_break (0); 552 json_decref (history); 553 return NULL; 554 } 555 } 556 break; 557 558 case TALER_EXCHANGEDB_TT_RESERVE_OPEN: 559 { 560 struct TALER_EXCHANGEDB_ReserveOpenListEntry *role 561 = pos->details.reserve_open; 562 563 if (0 != 564 json_array_append_new ( 565 history, 566 GNUNET_JSON_PACK ( 567 GNUNET_JSON_pack_string ("type", 568 "RESERVE-OPEN-DEPOSIT"), 569 GNUNET_JSON_pack_uint64 ("history_offset", 570 pos->coin_history_id), 571 TALER_JSON_pack_amount ("coin_contribution", 572 &role->coin_contribution), 573 GNUNET_JSON_pack_data_auto ("reserve_sig", 574 &role->reserve_sig), 575 GNUNET_JSON_pack_data_auto ("coin_sig", 576 &role->coin_sig)))) 577 { 578 GNUNET_break (0); 579 json_decref (history); 580 return NULL; 581 } 582 break; 583 } 584 } 585 } 586 return history; 587 } 588 589 590 MHD_RESULT 591 TEH_handler_coins_get (struct TEH_RequestContext *rc, 592 const struct TALER_CoinSpendPublicKeyP *coin_pub) 593 { 594 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 595 uint64_t start_off = 0; 596 uint64_t etag_in; 597 uint64_t etag_out; 598 char etagp[24]; 599 struct MHD_Response *resp; 600 unsigned int http_status; 601 struct TALER_DenominationHashP h_denom_pub; 602 struct TALER_Amount balance; 603 604 TALER_MHD_parse_request_number (rc->connection, 605 "start", 606 &start_off); 607 /* Check signature */ 608 { 609 struct TALER_CoinSpendSignatureP coin_sig; 610 bool required = true; 611 612 TALER_MHD_parse_request_header_auto (rc->connection, 613 TALER_COIN_HISTORY_SIGNATURE_HEADER, 614 &coin_sig, 615 required); 616 if (GNUNET_OK != 617 TALER_wallet_coin_history_verify (start_off, 618 coin_pub, 619 &coin_sig)) 620 { 621 GNUNET_break_op (0); 622 return TALER_MHD_reply_with_error (rc->connection, 623 MHD_HTTP_FORBIDDEN, 624 TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE, 625 NULL); 626 } 627 } 628 629 /* Get etag */ 630 { 631 const char *etags; 632 633 etags = MHD_lookup_connection_value (rc->connection, 634 MHD_HEADER_KIND, 635 MHD_HTTP_HEADER_IF_NONE_MATCH); 636 if (NULL != etags) 637 { 638 char dummy; 639 unsigned long long ev; 640 641 if (1 != sscanf (etags, 642 "\"%llu\"%c", 643 &ev, 644 &dummy)) 645 { 646 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 647 "Client send malformed `If-None-Match' header `%s'\n", 648 etags); 649 etag_in = start_off; 650 } 651 else 652 { 653 etag_in = (uint64_t) ev; 654 } 655 } 656 else 657 { 658 etag_in = start_off; 659 } 660 } 661 662 /* Get history from DB between etag and now */ 663 { 664 enum GNUNET_DB_QueryStatus qs; 665 666 qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, 667 true, 668 coin_pub, 669 start_off, 670 etag_in, 671 &etag_out, 672 &balance, 673 &h_denom_pub, 674 &tl); 675 switch (qs) 676 { 677 case GNUNET_DB_STATUS_HARD_ERROR: 678 GNUNET_break (0); 679 return TALER_MHD_reply_with_error (rc->connection, 680 MHD_HTTP_INTERNAL_SERVER_ERROR, 681 TALER_EC_GENERIC_DB_FETCH_FAILED, 682 "get_coin_history"); 683 case GNUNET_DB_STATUS_SOFT_ERROR: 684 return TALER_MHD_reply_with_error (rc->connection, 685 MHD_HTTP_INTERNAL_SERVER_ERROR, 686 TALER_EC_GENERIC_DB_SOFT_FAILURE, 687 "get_coin_history"); 688 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 689 return TALER_MHD_reply_with_error (rc->connection, 690 MHD_HTTP_NOT_FOUND, 691 TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN, 692 NULL); 693 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 694 /* Handled below */ 695 break; 696 } 697 } 698 699 GNUNET_snprintf (etagp, 700 sizeof (etagp), 701 "\"%llu\"", 702 (unsigned long long) etag_out); 703 if (etag_in == etag_out) 704 { 705 return TEH_RESPONSE_reply_not_modified (rc->connection, 706 etagp, 707 &add_response_headers, 708 NULL); 709 } 710 if (NULL == tl) 711 { 712 /* 204: empty history */ 713 resp = MHD_create_response_from_buffer_static (0, 714 ""); 715 http_status = MHD_HTTP_NO_CONTENT; 716 } 717 else 718 { 719 /* 200: regular history */ 720 json_t *history; 721 722 history = compile_transaction_history (coin_pub, 723 tl); 724 TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, 725 tl); 726 tl = NULL; 727 if (NULL == history) 728 { 729 GNUNET_break (0); 730 return TALER_MHD_reply_with_error (rc->connection, 731 MHD_HTTP_INTERNAL_SERVER_ERROR, 732 TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, 733 "Failed to compile coin history"); 734 } 735 resp = TALER_MHD_MAKE_JSON_PACK ( 736 GNUNET_JSON_pack_data_auto ("h_denom_pub", 737 &h_denom_pub), 738 TALER_JSON_pack_amount ("balance", 739 &balance), 740 GNUNET_JSON_pack_array_steal ("history", 741 history)); 742 http_status = MHD_HTTP_OK; 743 } 744 add_response_headers (NULL, 745 resp); 746 GNUNET_break (MHD_YES == 747 MHD_add_response_header (resp, 748 MHD_HTTP_HEADER_ETAG, 749 etagp)); 750 { 751 MHD_RESULT ret; 752 753 ret = MHD_queue_response (rc->connection, 754 http_status, 755 resp); 756 GNUNET_break (MHD_YES == ret); 757 MHD_destroy_response (resp); 758 return ret; 759 } 760 } 761 762 763 /* end of taler-exchange-httpd_coins_get.c */