LCOV - code coverage report
Current view: top level - lib - exchange_api_batch_withdraw2.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 0 199 0.0 %
Date: 2022-08-25 06:15:09 Functions: 0 5 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2022 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU 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 General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU General Public License along with
      14             :   TALER; see the file COPYING.  If not, see
      15             :   <http://www.gnu.org/licenses/>
      16             : */
      17             : /**
      18             :  * @file lib/exchange_api_batch_withdraw2.c
      19             :  * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests without blinding/unblinding
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <jansson.h>
      24             : #include <microhttpd.h> /* just for HTTP status codes */
      25             : #include <gnunet/gnunet_util_lib.h>
      26             : #include <gnunet/gnunet_json_lib.h>
      27             : #include <gnunet/gnunet_curl_lib.h>
      28             : #include "taler_exchange_service.h"
      29             : #include "taler_json_lib.h"
      30             : #include "exchange_api_handle.h"
      31             : #include "taler_signatures.h"
      32             : #include "exchange_api_curl_defaults.h"
      33             : 
      34             : 
      35             : /**
      36             :  * @brief A batch withdraw handle
      37             :  */
      38             : struct TALER_EXCHANGE_BatchWithdraw2Handle
      39             : {
      40             : 
      41             :   /**
      42             :    * The connection to exchange this request handle will use
      43             :    */
      44             :   struct TALER_EXCHANGE_Handle *exchange;
      45             : 
      46             :   /**
      47             :    * The url for this request.
      48             :    */
      49             :   char *url;
      50             : 
      51             :   /**
      52             :    * Handle for the request.
      53             :    */
      54             :   struct GNUNET_CURL_Job *job;
      55             : 
      56             :   /**
      57             :    * Function to call with the result.
      58             :    */
      59             :   TALER_EXCHANGE_BatchWithdraw2Callback cb;
      60             : 
      61             :   /**
      62             :    * Closure for @a cb.
      63             :    */
      64             :   void *cb_cls;
      65             : 
      66             :   /**
      67             :    * Context for #TEH_curl_easy_post(). Keeps the data that must
      68             :    * persist for Curl to make the upload.
      69             :    */
      70             :   struct TALER_CURL_PostContext post_ctx;
      71             : 
      72             :   /**
      73             :    * Total amount requested (value plus withdraw fee).
      74             :    */
      75             :   struct TALER_Amount requested_amount;
      76             : 
      77             :   /**
      78             :    * Public key of the reserve we are withdrawing from.
      79             :    */
      80             :   struct TALER_ReservePublicKeyP reserve_pub;
      81             : 
      82             :   /**
      83             :    * Number of coins expected.
      84             :    */
      85             :   unsigned int num_coins;
      86             : };
      87             : 
      88             : 
      89             : /**
      90             :  * We got a 200 OK response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
      91             :  * Extract the coin's signature and return it to the caller.  The signature we
      92             :  * get from the exchange is for the blinded value.  Thus, we first must
      93             :  * unblind it and then should verify its validity against our coin's hash.
      94             :  *
      95             :  * If everything checks out, we return the unblinded signature
      96             :  * to the application via the callback.
      97             :  *
      98             :  * @param wh operation handle
      99             :  * @param json reply from the exchange
     100             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     101             :  */
     102             : static enum GNUNET_GenericReturnValue
     103           0 : reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
     104             :                            const json_t *json)
     105           0 : {
     106           0 :   struct TALER_BlindedDenominationSignature blind_sigs[wh->num_coins];
     107           0 :   const json_t *ja = json_object_get (json,
     108             :                                       "ev_sigs");
     109             :   const json_t *j;
     110             :   unsigned int index;
     111           0 :   struct TALER_EXCHANGE_HttpResponse hr = {
     112             :     .reply = json,
     113             :     .http_status = MHD_HTTP_OK
     114             :   };
     115             : 
     116           0 :   if ( (NULL == ja) ||
     117           0 :        (! json_is_array (ja)) ||
     118           0 :        (wh->num_coins != json_array_size (ja)) )
     119             :   {
     120           0 :     GNUNET_break (0);
     121           0 :     return GNUNET_SYSERR;
     122             :   }
     123           0 :   json_array_foreach (ja, index, j)
     124             :   {
     125             :     struct GNUNET_JSON_Specification spec[] = {
     126           0 :       TALER_JSON_spec_blinded_denom_sig ("ev_sig",
     127             :                                          &blind_sigs[index]),
     128           0 :       GNUNET_JSON_spec_end ()
     129             :     };
     130             : 
     131           0 :     if (GNUNET_OK !=
     132           0 :         GNUNET_JSON_parse (j,
     133             :                            spec,
     134             :                            NULL, NULL))
     135             :     {
     136           0 :       GNUNET_break_op (0);
     137           0 :       for (unsigned int i = 0; i<index; i++)
     138           0 :         TALER_blinded_denom_sig_free (&blind_sigs[i]);
     139           0 :       return GNUNET_SYSERR;
     140             :     }
     141             :   }
     142             : 
     143             :   /* signature is valid, return it to the application */
     144           0 :   wh->cb (wh->cb_cls,
     145             :           &hr,
     146             :           blind_sigs,
     147             :           wh->num_coins);
     148             :   /* make sure callback isn't called again after return */
     149           0 :   wh->cb = NULL;
     150           0 :   for (unsigned int i = 0; i<wh->num_coins; i++)
     151           0 :     TALER_blinded_denom_sig_free (&blind_sigs[i]);
     152             : 
     153           0 :   return GNUNET_OK;
     154             : }
     155             : 
     156             : 
     157             : /**
     158             :  * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
     159             :  * Check the signatures on the batch withdraw transactions in the provided
     160             :  * history and that the balances add up.  We don't do anything directly
     161             :  * with the information, as the JSON will be returned to the application.
     162             :  * However, our job is ensuring that the exchange followed the protocol, and
     163             :  * this in particular means checking all of the signatures in the history.
     164             :  *
     165             :  * @param wh operation handle
     166             :  * @param json reply from the exchange
     167             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     168             :  */
     169             : static enum GNUNET_GenericReturnValue
     170           0 : reserve_batch_withdraw_payment_required (
     171             :   struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
     172             :   const json_t *json)
     173             : {
     174             :   struct TALER_Amount balance;
     175             :   struct TALER_Amount total_in_from_history;
     176             :   struct TALER_Amount total_out_from_history;
     177             :   json_t *history;
     178             :   size_t len;
     179             :   struct GNUNET_JSON_Specification spec[] = {
     180           0 :     TALER_JSON_spec_amount_any ("balance",
     181             :                                 &balance),
     182           0 :     GNUNET_JSON_spec_end ()
     183             :   };
     184             : 
     185           0 :   if (GNUNET_OK !=
     186           0 :       GNUNET_JSON_parse (json,
     187             :                          spec,
     188             :                          NULL, NULL))
     189             :   {
     190           0 :     GNUNET_break_op (0);
     191           0 :     return GNUNET_SYSERR;
     192             :   }
     193           0 :   history = json_object_get (json,
     194             :                              "history");
     195           0 :   if (NULL == history)
     196             :   {
     197           0 :     GNUNET_break_op (0);
     198           0 :     return GNUNET_SYSERR;
     199             :   }
     200             : 
     201             :   /* go over transaction history and compute
     202             :      total incoming and outgoing amounts */
     203           0 :   len = json_array_size (history);
     204             :   {
     205             :     struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
     206             : 
     207             :     /* Use heap allocation as "len" may be very big and thus this may
     208             :        not fit on the stack. Use "GNUNET_malloc_large" as a malicious
     209             :        exchange may theoretically try to crash us by giving a history
     210             :        that does not fit into our memory. */
     211           0 :     rhistory = GNUNET_malloc_large (
     212             :       sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
     213             :       * len);
     214           0 :     if (NULL == rhistory)
     215             :     {
     216           0 :       GNUNET_break (0);
     217           0 :       return GNUNET_SYSERR;
     218             :     }
     219             : 
     220           0 :     if (GNUNET_OK !=
     221           0 :         TALER_EXCHANGE_parse_reserve_history (wh->exchange,
     222             :                                               history,
     223           0 :                                               &wh->reserve_pub,
     224             :                                               balance.currency,
     225             :                                               &total_in_from_history,
     226             :                                               &total_out_from_history,
     227             :                                               len,
     228             :                                               rhistory))
     229             :     {
     230           0 :       GNUNET_break_op (0);
     231           0 :       TALER_EXCHANGE_free_reserve_history (rhistory,
     232             :                                            len);
     233           0 :       return GNUNET_SYSERR;
     234             :     }
     235           0 :     TALER_EXCHANGE_free_reserve_history (rhistory,
     236             :                                          len);
     237             :   }
     238             : 
     239             :   /* Check that funds were really insufficient */
     240           0 :   if (0 >= TALER_amount_cmp (&wh->requested_amount,
     241             :                              &balance))
     242             :   {
     243             :     /* Requested amount is smaller or equal to reported balance,
     244             :        so this should not have failed. */
     245           0 :     GNUNET_break_op (0);
     246           0 :     return GNUNET_SYSERR;
     247             :   }
     248           0 :   return GNUNET_OK;
     249             : }
     250             : 
     251             : 
     252             : /**
     253             :  * Function called when we're done processing the
     254             :  * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
     255             :  *
     256             :  * @param cls the `struct TALER_EXCHANGE_BatchWithdraw2Handle`
     257             :  * @param response_code HTTP response code, 0 on error
     258             :  * @param response parsed JSON result, NULL on error
     259             :  */
     260             : static void
     261           0 : handle_reserve_batch_withdraw_finished (void *cls,
     262             :                                         long response_code,
     263             :                                         const void *response)
     264             : {
     265           0 :   struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
     266           0 :   const json_t *j = response;
     267           0 :   struct TALER_EXCHANGE_HttpResponse hr = {
     268             :     .reply = j,
     269           0 :     .http_status = (unsigned int) response_code
     270             :   };
     271             : 
     272           0 :   wh->job = NULL;
     273           0 :   switch (response_code)
     274             :   {
     275           0 :   case 0:
     276           0 :     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     277           0 :     break;
     278           0 :   case MHD_HTTP_OK:
     279           0 :     if (GNUNET_OK !=
     280           0 :         reserve_batch_withdraw_ok (wh,
     281             :                                    j))
     282             :     {
     283           0 :       GNUNET_break_op (0);
     284           0 :       hr.http_status = 0;
     285           0 :       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     286           0 :       break;
     287             :     }
     288           0 :     GNUNET_assert (NULL == wh->cb);
     289           0 :     TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     290           0 :     return;
     291           0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     292             :     /* only validate reply is well-formed */
     293             :     {
     294             :       uint64_t ptu;
     295             :       struct GNUNET_JSON_Specification spec[] = {
     296           0 :         GNUNET_JSON_spec_uint64 ("legitimization_uuid",
     297             :                                  &ptu),
     298           0 :         GNUNET_JSON_spec_end ()
     299             :       };
     300             : 
     301           0 :       if (GNUNET_OK !=
     302           0 :           GNUNET_JSON_parse (j,
     303             :                              spec,
     304             :                              NULL, NULL))
     305             :       {
     306           0 :         GNUNET_break_op (0);
     307           0 :         hr.http_status = 0;
     308           0 :         hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     309           0 :         break;
     310             :       }
     311             :     }
     312           0 :     break;
     313           0 :   case MHD_HTTP_BAD_REQUEST:
     314             :     /* This should never happen, either us or the exchange is buggy
     315             :        (or API version conflict); just pass JSON reply to the application */
     316           0 :     hr.ec = TALER_JSON_get_error_code (j);
     317           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     318           0 :     break;
     319           0 :   case MHD_HTTP_FORBIDDEN:
     320           0 :     GNUNET_break_op (0);
     321             :     /* Nothing really to verify, exchange says one of the signatures is
     322             :        invalid; as we checked them, this should never happen, we
     323             :        should pass the JSON reply to the application */
     324           0 :     hr.ec = TALER_JSON_get_error_code (j);
     325           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     326           0 :     break;
     327           0 :   case MHD_HTTP_NOT_FOUND:
     328             :     /* Nothing really to verify, the exchange basically just says
     329             :        that it doesn't know this reserve.  Can happen if we
     330             :        query before the wire transfer went through.
     331             :        We should simply pass the JSON reply to the application. */
     332           0 :     hr.ec = TALER_JSON_get_error_code (j);
     333           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     334           0 :     break;
     335           0 :   case MHD_HTTP_CONFLICT:
     336             :     /* The exchange says that the reserve has insufficient funds;
     337             :        check the signatures in the history... */
     338           0 :     if (GNUNET_OK !=
     339           0 :         reserve_batch_withdraw_payment_required (wh,
     340             :                                                  j))
     341             :     {
     342           0 :       GNUNET_break_op (0);
     343           0 :       hr.http_status = 0;
     344           0 :       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     345             :     }
     346             :     else
     347             :     {
     348           0 :       hr.ec = TALER_JSON_get_error_code (j);
     349           0 :       hr.hint = TALER_JSON_get_error_hint (j);
     350             :     }
     351           0 :     break;
     352           0 :   case MHD_HTTP_GONE:
     353             :     /* could happen if denomination was revoked */
     354             :     /* Note: one might want to check /keys for revocation
     355             :        signature here, alas tricky in case our /keys
     356             :        is outdated => left to clients */
     357           0 :     hr.ec = TALER_JSON_get_error_code (j);
     358           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     359           0 :     break;
     360           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     361             :     /* Server had an internal issue; we should retry, but this API
     362             :        leaves this to the application */
     363           0 :     hr.ec = TALER_JSON_get_error_code (j);
     364           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     365           0 :     break;
     366           0 :   default:
     367             :     /* unexpected response code */
     368           0 :     GNUNET_break_op (0);
     369           0 :     hr.ec = TALER_JSON_get_error_code (j);
     370           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     371           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     372             :                 "Unexpected response code %u/%d for exchange batch withdraw\n",
     373             :                 (unsigned int) response_code,
     374             :                 (int) hr.ec);
     375           0 :     break;
     376             :   }
     377           0 :   if (NULL != wh->cb)
     378             :   {
     379           0 :     wh->cb (wh->cb_cls,
     380             :             &hr,
     381             :             NULL,
     382             :             0);
     383           0 :     wh->cb = NULL;
     384             :   }
     385           0 :   TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     386             : }
     387             : 
     388             : 
     389             : struct TALER_EXCHANGE_BatchWithdraw2Handle *
     390           0 : TALER_EXCHANGE_batch_withdraw2 (
     391             :   struct TALER_EXCHANGE_Handle *exchange,
     392             :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     393             :   const struct TALER_PlanchetDetail *pds,
     394             :   unsigned int pds_length,
     395             :   TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
     396             :   void *res_cb_cls)
     397             : {
     398             :   struct TALER_EXCHANGE_BatchWithdraw2Handle *wh;
     399             :   const struct TALER_EXCHANGE_Keys *keys;
     400             :   const struct TALER_EXCHANGE_DenomPublicKey *dk;
     401             :   struct TALER_ReserveSignatureP reserve_sig;
     402             :   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
     403             :   struct TALER_BlindedCoinHashP bch;
     404             :   json_t *jc;
     405             : 
     406           0 :   keys = TALER_EXCHANGE_get_keys (exchange);
     407           0 :   if (NULL == keys)
     408             :   {
     409           0 :     GNUNET_break (0);
     410           0 :     return NULL;
     411             :   }
     412           0 :   wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle);
     413           0 :   wh->exchange = exchange;
     414           0 :   wh->cb = res_cb;
     415           0 :   wh->cb_cls = res_cb_cls;
     416           0 :   wh->num_coins = pds_length;
     417           0 :   GNUNET_assert (GNUNET_OK ==
     418             :                  TALER_amount_set_zero (keys->currency,
     419             :                                         &wh->requested_amount));
     420           0 :   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
     421             :                                       &wh->reserve_pub.eddsa_pub);
     422             :   {
     423             :     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
     424             :     char *end;
     425             : 
     426           0 :     end = GNUNET_STRINGS_data_to_string (
     427           0 :       &wh->reserve_pub,
     428             :       sizeof (struct TALER_ReservePublicKeyP),
     429             :       pub_str,
     430             :       sizeof (pub_str));
     431           0 :     *end = '\0';
     432           0 :     GNUNET_snprintf (arg_str,
     433             :                      sizeof (arg_str),
     434             :                      "/reserves/%s/batch-withdraw",
     435             :                      pub_str);
     436             :   }
     437           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     438             :               "Attempting to batch-withdraw from reserve %s\n",
     439             :               TALER_B2S (&wh->reserve_pub));
     440           0 :   wh->url = TEAH_path_to_url (exchange,
     441             :                               arg_str);
     442           0 :   if (NULL == wh->url)
     443             :   {
     444           0 :     GNUNET_break (0);
     445           0 :     TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     446           0 :     return NULL;
     447             :   }
     448           0 :   jc = json_array ();
     449           0 :   GNUNET_assert (NULL != jc);
     450           0 :   for (unsigned int i = 0; i<pds_length; i++)
     451             :   {
     452           0 :     const struct TALER_PlanchetDetail *pd = &pds[i];
     453             :     struct TALER_Amount coin_total;
     454             :     json_t *withdraw_obj;
     455             : 
     456           0 :     dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
     457             :                                                       &pd->denom_pub_hash);
     458           0 :     if (NULL == dk)
     459             :     {
     460           0 :       TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     461           0 :       json_decref (jc);
     462           0 :       GNUNET_break (0);
     463           0 :       return NULL;
     464             :     }
     465             :     /* Compute how much we expected to charge to the reserve */
     466           0 :     if (0 >
     467           0 :         TALER_amount_add (&coin_total,
     468             :                           &dk->fees.withdraw,
     469             :                           &dk->value))
     470             :     {
     471             :       /* Overflow here? Very strange, our CPU must be fried... */
     472           0 :       GNUNET_break (0);
     473           0 :       TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     474           0 :       json_decref (jc);
     475           0 :       return NULL;
     476             :     }
     477           0 :     if (0 >
     478           0 :         TALER_amount_add (&wh->requested_amount,
     479           0 :                           &wh->requested_amount,
     480             :                           &coin_total))
     481             :     {
     482             :       /* Overflow here? Very strange, our CPU must be fried... */
     483           0 :       GNUNET_break (0);
     484           0 :       TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     485           0 :       json_decref (jc);
     486           0 :       return NULL;
     487             :     }
     488           0 :     if (GNUNET_OK !=
     489           0 :         TALER_coin_ev_hash (&pd->blinded_planchet,
     490             :                             &pd->denom_pub_hash,
     491             :                             &bch))
     492             :     {
     493           0 :       GNUNET_break (0);
     494           0 :       TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     495           0 :       json_decref (jc);
     496           0 :       return NULL;
     497             :     }
     498           0 :     TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
     499             :                                 &coin_total,
     500             :                                 &bch,
     501             :                                 reserve_priv,
     502             :                                 &reserve_sig);
     503           0 :     withdraw_obj = GNUNET_JSON_PACK (
     504             :       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
     505             :                                   &pd->denom_pub_hash),
     506             :       TALER_JSON_pack_blinded_planchet ("coin_ev",
     507             :                                         &pd->blinded_planchet),
     508             :       GNUNET_JSON_pack_data_auto ("reserve_sig",
     509             :                                   &reserve_sig));
     510           0 :     GNUNET_assert (NULL != withdraw_obj);
     511           0 :     GNUNET_assert (0 ==
     512             :                    json_array_append_new (jc,
     513             :                                           withdraw_obj));
     514             :   }
     515             :   {
     516             :     CURL *eh;
     517             :     struct GNUNET_CURL_Context *ctx;
     518             :     json_t *req;
     519             : 
     520           0 :     req = GNUNET_JSON_PACK (
     521             :       GNUNET_JSON_pack_array_steal ("planchets",
     522             :                                     jc));
     523           0 :     ctx = TEAH_handle_to_context (exchange);
     524           0 :     eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
     525           0 :     if ( (NULL == eh) ||
     526             :          (GNUNET_OK !=
     527           0 :           TALER_curl_easy_post (&wh->post_ctx,
     528             :                                 eh,
     529             :                                 req)) )
     530             :     {
     531           0 :       GNUNET_break (0);
     532           0 :       if (NULL != eh)
     533           0 :         curl_easy_cleanup (eh);
     534           0 :       json_decref (req);
     535           0 :       TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     536           0 :       return NULL;
     537             :     }
     538           0 :     json_decref (req);
     539           0 :     wh->job = GNUNET_CURL_job_add2 (ctx,
     540             :                                     eh,
     541           0 :                                     wh->post_ctx.headers,
     542             :                                     &handle_reserve_batch_withdraw_finished,
     543             :                                     wh);
     544             :   }
     545           0 :   return wh;
     546             : }
     547             : 
     548             : 
     549             : void
     550           0 : TALER_EXCHANGE_batch_withdraw2_cancel (
     551             :   struct TALER_EXCHANGE_BatchWithdraw2Handle *wh)
     552             : {
     553           0 :   if (NULL != wh->job)
     554             :   {
     555           0 :     GNUNET_CURL_job_cancel (wh->job);
     556           0 :     wh->job = NULL;
     557             :   }
     558           0 :   GNUNET_free (wh->url);
     559           0 :   TALER_curl_easy_post_finished (&wh->post_ctx);
     560           0 :   GNUNET_free (wh);
     561           0 : }

Generated by: LCOV version 1.14