merchant_api_get-fountain-info.c (10287B)
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_get-fountain-info.c 21 * @brief Implementation of the (public) GET /fountain/info 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/get-fountain-info.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 GET /fountain/info operation. 39 */ 40 struct TALER_MERCHANT_GetFountainInfoHandle 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 * HTTP headers (Authorization) for the request. 54 */ 55 struct curl_slist *headers; 56 57 /** 58 * Handle for the request. 59 */ 60 struct GNUNET_CURL_Job *job; 61 62 /** 63 * Function to call with the result. 64 */ 65 TALER_MERCHANT_GetFountainInfoCallback cb; 66 67 /** 68 * Closure for @a cb. 69 */ 70 TALER_MERCHANT_GET_FOUNTAIN_INFO_RESULT_CLOSURE *cb_cls; 71 72 /** 73 * Reference to the execution context. 74 */ 75 struct GNUNET_CURL_Context *ctx; 76 }; 77 78 79 /** 80 * Parse the fountain info and invoke the callback. 81 * 82 * @param gfih operation handle 83 * @param[in,out] fir response to complete and pass to the callback 84 */ 85 static void 86 handle_ok (struct TALER_MERCHANT_GetFountainInfoHandle *gfih, 87 struct TALER_MERCHANT_GetFountainInfoResponse *fir) 88 { 89 const json_t *jgrants; 90 struct GNUNET_JSON_Specification spec[] = { 91 GNUNET_JSON_spec_relative_time ("poll_freq", 92 &fir->details.ok.poll_freq), 93 GNUNET_JSON_spec_array_const ("grants", 94 &jgrants), 95 GNUNET_JSON_spec_end () 96 }; 97 98 if (GNUNET_OK != 99 GNUNET_JSON_parse (fir->hr.reply, 100 spec, 101 NULL, 102 NULL)) 103 { 104 GNUNET_break_op (0); 105 fir->hr.http_status = 0; 106 fir->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 107 gfih->cb (gfih->cb_cls, 108 fir); 109 return; 110 } 111 if (json_array_size (jgrants) > TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS) 112 { 113 fir->hr.http_status = 0; 114 fir->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 115 gfih->cb (gfih->cb_cls, 116 fir); 117 return; 118 } 119 { 120 unsigned int len = (unsigned int) json_array_size (jgrants); 121 struct TALER_MERCHANT_FountainWalletGrant *grants 122 = GNUNET_new_array (GNUNET_NZL (len), 123 struct TALER_MERCHANT_FountainWalletGrant); 124 unsigned int parsed = 0; 125 bool fail = false; 126 size_t idx; 127 json_t *jg; 128 129 json_array_foreach ((json_t *) jgrants, idx, jg) 130 { 131 struct TALER_MERCHANT_FountainWalletGrant *wg = &grants[idx]; 132 const json_t *jfamily; 133 uint32_t window; 134 struct GNUNET_JSON_Specification ispec[] = { 135 GNUNET_JSON_spec_string ("token_family_slug", 136 &wg->grant.token_family_slug), 137 GNUNET_JSON_spec_uint64 ("tokens_per_period_limit", 138 &wg->grant.tokens_per_period_limit), 139 GNUNET_JSON_spec_uint64 ("tokens_per_period_stash", 140 &wg->grant.tokens_per_period_stash), 141 GNUNET_JSON_spec_uint32 ("key_window_size", 142 &window), 143 GNUNET_JSON_spec_object_const ("token_family", 144 &jfamily), 145 GNUNET_JSON_spec_end () 146 }; 147 148 if (GNUNET_OK != 149 GNUNET_JSON_parse (jg, 150 ispec, 151 NULL, 152 NULL)) 153 { 154 GNUNET_break_op (0); 155 fail = true; 156 break; 157 } 158 wg->grant.key_window_size = window; 159 /* The single-family parser expects an object mapping slugs to 160 families (as in contract terms); wrap accordingly. */ 161 { 162 json_t *jwrap; 163 struct TALER_MERCHANT_ContractTokenFamily *families = NULL; 164 unsigned int families_len = 0; 165 struct GNUNET_JSON_Specification wspec[] = { 166 TALER_MERCHANT_spec_token_families (NULL, 167 &families, 168 &families_len), 169 GNUNET_JSON_spec_end () 170 }; 171 172 jwrap = GNUNET_JSON_PACK ( 173 GNUNET_JSON_pack_object_incref ( 174 wg->grant.token_family_slug, 175 (json_t *) jfamily)); 176 if ( (GNUNET_OK != 177 GNUNET_JSON_parse (jwrap, 178 wspec, 179 NULL, 180 NULL)) || 181 (1 != families_len) ) 182 { 183 GNUNET_break_op (0); 184 json_decref (jwrap); 185 for (unsigned int i = 0; i < families_len; i++) 186 TALER_MERCHANT_contract_token_family_free (&families[i]); 187 GNUNET_free (families); 188 fail = true; 189 break; 190 } 191 json_decref (jwrap); 192 wg->token_family = families[0]; 193 GNUNET_free (families); 194 } 195 parsed++; 196 } 197 if (fail) 198 { 199 for (unsigned int i = 0; i < parsed; i++) 200 TALER_MERCHANT_contract_token_family_free (&grants[i].token_family); 201 GNUNET_free (grants); 202 fir->hr.http_status = 0; 203 fir->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 204 gfih->cb (gfih->cb_cls, 205 fir); 206 return; 207 } 208 fir->details.ok.grants = grants; 209 fir->details.ok.grants_len = len; 210 gfih->cb (gfih->cb_cls, 211 fir); 212 for (unsigned int i = 0; i < len; i++) 213 TALER_MERCHANT_contract_token_family_free (&grants[i].token_family); 214 GNUNET_free (grants); 215 } 216 } 217 218 219 /** 220 * Function called when we're done processing the 221 * HTTP GET /fountain/info request. 222 * 223 * @param cls the `struct TALER_MERCHANT_GetFountainInfoHandle` 224 * @param response_code HTTP response code, 0 on error 225 * @param response response body, NULL if not in JSON 226 */ 227 static void 228 handle_get_fountain_info_finished (void *cls, 229 long response_code, 230 const void *response) 231 { 232 struct TALER_MERCHANT_GetFountainInfoHandle *gfih = cls; 233 const json_t *json = response; 234 struct TALER_MERCHANT_GetFountainInfoResponse fir = { 235 .hr.http_status = (unsigned int) response_code, 236 .hr.reply = json 237 }; 238 239 gfih->job = NULL; 240 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 241 "GET /fountain/info completed with response code %u\n", 242 (unsigned int) response_code); 243 switch (response_code) 244 { 245 case 0: 246 fir.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 247 break; 248 case MHD_HTTP_OK: 249 handle_ok (gfih, 250 &fir); 251 TALER_MERCHANT_get_fountain_info_cancel (gfih); 252 return; 253 case MHD_HTTP_UNAUTHORIZED: 254 case MHD_HTTP_NOT_FOUND: 255 case MHD_HTTP_INTERNAL_SERVER_ERROR: 256 fir.hr.ec = TALER_JSON_get_error_code (json); 257 fir.hr.hint = TALER_JSON_get_error_hint (json); 258 break; 259 default: 260 TALER_MERCHANT_parse_error_details_ (json, 261 response_code, 262 &fir.hr); 263 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 264 "Unexpected response code %u/%d\n", 265 (unsigned int) response_code, 266 (int) fir.hr.ec); 267 GNUNET_break_op (0); 268 break; 269 } 270 gfih->cb (gfih->cb_cls, 271 &fir); 272 TALER_MERCHANT_get_fountain_info_cancel (gfih); 273 } 274 275 276 struct TALER_MERCHANT_GetFountainInfoHandle * 277 TALER_MERCHANT_get_fountain_info_create ( 278 struct GNUNET_CURL_Context *ctx, 279 const char *url, 280 const char *fountain_secret) 281 { 282 struct TALER_MERCHANT_GetFountainInfoHandle *gfih; 283 284 gfih = GNUNET_new (struct TALER_MERCHANT_GetFountainInfoHandle); 285 gfih->ctx = ctx; 286 gfih->base_url = GNUNET_strdup (url); 287 { 288 char *hdr; 289 290 GNUNET_asprintf (&hdr, 291 "%s: Bearer %s", 292 MHD_HTTP_HEADER_AUTHORIZATION, 293 fountain_secret); 294 gfih->headers = curl_slist_append (NULL, 295 hdr); 296 GNUNET_free (hdr); 297 } 298 return gfih; 299 } 300 301 302 enum TALER_ErrorCode 303 TALER_MERCHANT_get_fountain_info_start ( 304 struct TALER_MERCHANT_GetFountainInfoHandle *gfih, 305 TALER_MERCHANT_GetFountainInfoCallback cb, 306 TALER_MERCHANT_GET_FOUNTAIN_INFO_RESULT_CLOSURE *cb_cls) 307 { 308 CURL *eh; 309 310 gfih->cb = cb; 311 gfih->cb_cls = cb_cls; 312 gfih->url = TALER_url_join (gfih->base_url, 313 "fountain/info", 314 NULL); 315 if (NULL == gfih->url) 316 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 317 eh = TALER_MERCHANT_curl_easy_get_ (gfih->url); 318 if (NULL == eh) 319 { 320 GNUNET_break (0); 321 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 322 } 323 gfih->job = GNUNET_CURL_job_add2 (gfih->ctx, 324 eh, 325 gfih->headers, 326 &handle_get_fountain_info_finished, 327 gfih); 328 if (NULL == gfih->job) 329 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 330 return TALER_EC_NONE; 331 } 332 333 334 void 335 TALER_MERCHANT_get_fountain_info_cancel ( 336 struct TALER_MERCHANT_GetFountainInfoHandle *gfih) 337 { 338 if (NULL != gfih->job) 339 { 340 GNUNET_CURL_job_cancel (gfih->job); 341 gfih->job = NULL; 342 } 343 curl_slist_free_all (gfih->headers); 344 GNUNET_free (gfih->url); 345 GNUNET_free (gfih->base_url); 346 GNUNET_free (gfih); 347 } 348 349 350 /* end of merchant_api_get-fountain-info.c */