merchant_api_post-fountain-withdraw.c (12233B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General 16 Public License along with TALER; see the file COPYING.LGPL. 17 If not, see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file src/lib/merchant_api_post-fountain-withdraw.c 21 * @brief Implementation of the (public) POST /fountain/withdraw request 22 * @author Bohdan Potuzhnyi 23 */ 24 #include "platform.h" 25 #include <curl/curl.h> 26 #include <jansson.h> 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include <gnunet/gnunet_util_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include <taler/merchant/post-fountain-withdraw.h> 31 #include "merchant_api_curl_defaults.h" 32 #include "merchant_api_common.h" 33 #include <taler/taler_json_lib.h> 34 #include <taler/taler_curl_lib.h> 35 36 37 /** 38 * Handle for a POST /fountain/withdraw operation. 39 */ 40 struct TALER_MERCHANT_PostFountainWithdrawHandle 41 { 42 /** 43 * Base URL of the merchant backend. 44 */ 45 char *base_url; 46 47 /** 48 * The full URL for this request. 49 */ 50 char *url; 51 52 /** 53 * Handle for the request. 54 */ 55 struct GNUNET_CURL_Job *job; 56 57 /** 58 * Function to call with the result. 59 */ 60 TALER_MERCHANT_PostFountainWithdrawCallback cb; 61 62 /** 63 * Closure for @a cb. 64 */ 65 TALER_MERCHANT_POST_FOUNTAIN_WITHDRAW_RESULT_CLOSURE *cb_cls; 66 67 /** 68 * Reference to the execution context. 69 */ 70 struct GNUNET_CURL_Context *ctx; 71 72 /** 73 * Minor context that holds body and headers. 74 */ 75 struct TALER_CURL_PostContext post_ctx; 76 77 /** 78 * Request body. 79 */ 80 json_t *req_obj; 81 }; 82 83 84 /** 85 * Parse the withdraw results and invoke the callback. 86 * 87 * @param pfwh operation handle 88 * @param[in,out] fwr response to complete and pass to the callback 89 */ 90 static void 91 handle_ok (struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh, 92 struct TALER_MERCHANT_PostFountainWithdrawResponse *fwr) 93 { 94 const json_t *jresults; 95 struct GNUNET_JSON_Specification spec[] = { 96 GNUNET_JSON_spec_array_const ("grants", 97 &jresults), 98 GNUNET_JSON_spec_end () 99 }; 100 101 if (GNUNET_OK != 102 GNUNET_JSON_parse (fwr->hr.reply, 103 spec, 104 NULL, 105 NULL)) 106 { 107 GNUNET_break_op (0); 108 fwr->hr.http_status = 0; 109 fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 110 pfwh->cb (pfwh->cb_cls, 111 fwr); 112 return; 113 } 114 if (json_array_size (jresults) > TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS) 115 { 116 fwr->hr.http_status = 0; 117 fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 118 pfwh->cb (pfwh->cb_cls, 119 fwr); 120 return; 121 } 122 { 123 unsigned int len = (unsigned int) json_array_size (jresults); 124 struct TALER_MERCHANT_FountainWithdrawResult *results; 125 struct TALER_BlindedTokenIssueSignature *all_sigs = NULL; 126 unsigned int num_all_sigs = 0; 127 bool fail = false; 128 size_t idx; 129 json_t *jr; 130 131 /* Bound the total before adding, narrowing or allocating. */ 132 json_array_foreach ((json_t *) jresults, idx, jr) 133 { 134 const json_t *jsigs = json_object_get (jr, 135 "token_sigs"); 136 137 if ( (! json_is_array (jsigs)) || 138 (json_array_size (jsigs) > 139 TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS - num_all_sigs) ) 140 { 141 fwr->hr.http_status = 0; 142 fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 143 pfwh->cb (pfwh->cb_cls, 144 fwr); 145 return; 146 } 147 num_all_sigs += (unsigned int) json_array_size (jsigs); 148 } 149 results = GNUNET_new_array (GNUNET_NZL (len), 150 struct TALER_MERCHANT_FountainWithdrawResult); 151 all_sigs = GNUNET_new_array (GNUNET_NZL (num_all_sigs), 152 struct TALER_BlindedTokenIssueSignature); 153 num_all_sigs = 0; 154 json_array_foreach ((json_t *) jresults, idx, jr) 155 { 156 struct TALER_MERCHANT_FountainWithdrawResult *res = &results[idx]; 157 const json_t *jsigs; 158 struct GNUNET_JSON_Specification ispec[] = { 159 GNUNET_JSON_spec_string ("token_family_slug", 160 &res->token_family_slug), 161 GNUNET_JSON_spec_fixed_auto ("h_issue", 162 &res->h_issue), 163 GNUNET_JSON_spec_array_const ("token_sigs", 164 &jsigs), 165 GNUNET_JSON_spec_end () 166 }; 167 168 if (GNUNET_OK != 169 GNUNET_JSON_parse (jr, 170 ispec, 171 NULL, 172 NULL)) 173 { 174 GNUNET_break_op (0); 175 fail = true; 176 break; 177 } 178 res->token_sigs = &all_sigs[num_all_sigs]; 179 res->num_sigs = (unsigned int) json_array_size (jsigs); 180 { 181 size_t sidx; 182 json_t *js; 183 184 json_array_foreach ((json_t *) jsigs, sidx, js) 185 { 186 struct GNUNET_JSON_Specification sspec[] = { 187 GNUNET_JSON_spec_blinded_signature ( 188 "blind_sig", 189 &all_sigs[num_all_sigs].signature), 190 GNUNET_JSON_spec_end () 191 }; 192 193 if (GNUNET_OK != 194 GNUNET_JSON_parse (js, 195 sspec, 196 NULL, 197 NULL)) 198 { 199 GNUNET_break_op (0); 200 fail = true; 201 break; 202 } 203 num_all_sigs++; 204 } 205 } 206 if (fail) 207 break; 208 } 209 if (fail) 210 { 211 fwr->hr.http_status = 0; 212 fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 213 pfwh->cb (pfwh->cb_cls, 214 fwr); 215 } 216 else 217 { 218 fwr->details.ok.results = results; 219 fwr->details.ok.results_len = len; 220 pfwh->cb (pfwh->cb_cls, 221 fwr); 222 } 223 for (unsigned int i = 0; i < num_all_sigs; i++) 224 if (NULL != all_sigs[i].signature) 225 GNUNET_CRYPTO_blinded_sig_decref (all_sigs[i].signature); 226 GNUNET_free (all_sigs); 227 GNUNET_free (results); 228 } 229 } 230 231 232 /** 233 * Function called when we're done processing the 234 * HTTP POST /fountain/withdraw request. 235 * 236 * @param cls the `struct TALER_MERCHANT_PostFountainWithdrawHandle` 237 * @param response_code HTTP response code, 0 on error 238 * @param response response body, NULL if not in JSON 239 */ 240 static void 241 handle_post_fountain_withdraw_finished (void *cls, 242 long response_code, 243 const void *response) 244 { 245 struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh = cls; 246 const json_t *json = response; 247 struct TALER_MERCHANT_PostFountainWithdrawResponse fwr = { 248 .hr.http_status = (unsigned int) response_code, 249 .hr.reply = json 250 }; 251 252 pfwh->job = NULL; 253 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 254 "POST /fountain/withdraw completed with response code %u\n", 255 (unsigned int) response_code); 256 switch (response_code) 257 { 258 case 0: 259 fwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 260 break; 261 case MHD_HTTP_OK: 262 handle_ok (pfwh, 263 &fwr); 264 TALER_MERCHANT_post_fountain_withdraw_cancel (pfwh); 265 return; 266 case MHD_HTTP_BAD_REQUEST: 267 case MHD_HTTP_UNAUTHORIZED: 268 case MHD_HTTP_NOT_FOUND: 269 case MHD_HTTP_CONFLICT: 270 case MHD_HTTP_TOO_MANY_REQUESTS: 271 case MHD_HTTP_INTERNAL_SERVER_ERROR: 272 fwr.hr.ec = TALER_JSON_get_error_code (json); 273 fwr.hr.hint = TALER_JSON_get_error_hint (json); 274 break; 275 default: 276 TALER_MERCHANT_parse_error_details_ (json, 277 response_code, 278 &fwr.hr); 279 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 280 "Unexpected response code %u/%d\n", 281 (unsigned int) response_code, 282 (int) fwr.hr.ec); 283 GNUNET_break_op (0); 284 break; 285 } 286 pfwh->cb (pfwh->cb_cls, 287 &fwr); 288 TALER_MERCHANT_post_fountain_withdraw_cancel (pfwh); 289 } 290 291 292 struct TALER_MERCHANT_PostFountainWithdrawHandle * 293 TALER_MERCHANT_post_fountain_withdraw_create ( 294 struct GNUNET_CURL_Context *ctx, 295 const char *url, 296 const char *fountain_secret, 297 unsigned int num_entries, 298 const struct TALER_MERCHANT_FountainWithdrawEntry *entries) 299 { 300 struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh; 301 json_t *jentries; 302 303 jentries = json_array (); 304 GNUNET_assert (NULL != jentries); 305 for (unsigned int i = 0; i < num_entries; i++) 306 { 307 const struct TALER_MERCHANT_FountainWithdrawEntry *we = &entries[i]; 308 json_t *jenvelopes; 309 310 jenvelopes = json_array (); 311 GNUNET_assert (NULL != jenvelopes); 312 for (unsigned int j = 0; j < we->num_envelopes; j++) 313 GNUNET_assert (0 == 314 json_array_append_new ( 315 jenvelopes, 316 GNUNET_JSON_PACK ( 317 TALER_JSON_pack_token_envelope (NULL, 318 &we->envelopes[j])))); 319 GNUNET_assert (0 == 320 json_array_append_new ( 321 jentries, 322 GNUNET_JSON_PACK ( 323 GNUNET_JSON_pack_string ("token_family_slug", 324 we->token_family_slug), 325 GNUNET_JSON_pack_allow_null ( 326 GNUNET_TIME_absolute_is_zero ( 327 we->valid_at.abs_time) 328 ? GNUNET_JSON_pack_string ("valid_at", 329 NULL) 330 : GNUNET_JSON_pack_timestamp ("valid_at", 331 we->valid_at)), 332 GNUNET_JSON_pack_array_steal ("envelopes", 333 jenvelopes)))); 334 } 335 pfwh = GNUNET_new (struct TALER_MERCHANT_PostFountainWithdrawHandle); 336 pfwh->ctx = ctx; 337 pfwh->base_url = GNUNET_strdup (url); 338 pfwh->req_obj = GNUNET_JSON_PACK ( 339 GNUNET_JSON_pack_string ("fountain_secret", 340 fountain_secret), 341 GNUNET_JSON_pack_array_steal ("grants", 342 jentries)); 343 return pfwh; 344 } 345 346 347 enum TALER_ErrorCode 348 TALER_MERCHANT_post_fountain_withdraw_start ( 349 struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh, 350 TALER_MERCHANT_PostFountainWithdrawCallback cb, 351 TALER_MERCHANT_POST_FOUNTAIN_WITHDRAW_RESULT_CLOSURE *cb_cls) 352 { 353 CURL *eh; 354 355 pfwh->cb = cb; 356 pfwh->cb_cls = cb_cls; 357 pfwh->url = TALER_url_join (pfwh->base_url, 358 "fountain/withdraw", 359 NULL); 360 if (NULL == pfwh->url) 361 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 362 eh = TALER_MERCHANT_curl_easy_get_ (pfwh->url); 363 if ( (NULL == eh) || 364 (GNUNET_OK != 365 TALER_curl_easy_post (&pfwh->post_ctx, 366 eh, 367 pfwh->req_obj)) ) 368 { 369 GNUNET_break (0); 370 if (NULL != eh) 371 curl_easy_cleanup (eh); 372 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 373 } 374 pfwh->job = GNUNET_CURL_job_add2 (pfwh->ctx, 375 eh, 376 pfwh->post_ctx.headers, 377 &handle_post_fountain_withdraw_finished, 378 pfwh); 379 if (NULL == pfwh->job) 380 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 381 return TALER_EC_NONE; 382 } 383 384 385 void 386 TALER_MERCHANT_post_fountain_withdraw_cancel ( 387 struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh) 388 { 389 if (NULL != pfwh->job) 390 { 391 GNUNET_CURL_job_cancel (pfwh->job); 392 pfwh->job = NULL; 393 } 394 TALER_curl_easy_post_finished (&pfwh->post_ctx); 395 json_decref (pfwh->req_obj); 396 GNUNET_free (pfwh->url); 397 GNUNET_free (pfwh->base_url); 398 GNUNET_free (pfwh); 399 } 400 401 402 /* end of merchant_api_post-fountain-withdraw.c */