merchant_api_get-templates-TEMPLATE_ID.c (13396B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, 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 Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file src/lib/merchant_api_get-templates-TEMPLATE_ID.c 19 * @brief Implementation of the GET /templates/$TEMPLATE_ID request (wallet-facing) 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include <taler/merchant/get-templates-TEMPLATE_ID.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of exchange candidates or wire methods accepted in a reply. 35 */ 36 #define MAX_CANDIDATE_ENTRIES 1024 37 38 39 /** 40 * Free merchant metadata parsed for a callback. 41 * 42 * @param merchant metadata to free 43 */ 44 static void 45 free_merchant_metadata (struct TALER_MERCHANT_MetaData *merchant) 46 { 47 GNUNET_free (merchant->name); 48 GNUNET_free (merchant->website); 49 GNUNET_free (merchant->email); 50 GNUNET_free (merchant->logo); 51 if (NULL != merchant->address) 52 json_decref (merchant->address); 53 if (NULL != merchant->jurisdiction) 54 json_decref (merchant->jurisdiction); 55 } 56 57 58 /** 59 * Parse merchant metadata. 60 * 61 * @param object merchant JSON object 62 * @param[out] merchant parsed metadata 63 * @return #GNUNET_OK on success 64 */ 65 static enum GNUNET_GenericReturnValue 66 parse_merchant_metadata (const json_t *object, 67 struct TALER_MERCHANT_MetaData *merchant) 68 { 69 struct GNUNET_JSON_Specification spec[] = { 70 GNUNET_JSON_spec_string_copy ("name", 71 &merchant->name), 72 GNUNET_JSON_spec_mark_optional ( 73 TALER_JSON_spec_web_url_copy ("website", 74 &merchant->website), 75 NULL), 76 GNUNET_JSON_spec_mark_optional ( 77 GNUNET_JSON_spec_string_copy ("email", 78 &merchant->email), 79 NULL), 80 GNUNET_JSON_spec_mark_optional ( 81 GNUNET_JSON_spec_string_copy ("logo", 82 &merchant->logo), 83 NULL), 84 GNUNET_JSON_spec_mark_optional ( 85 GNUNET_JSON_spec_object_copy ("address", 86 &merchant->address), 87 NULL), 88 GNUNET_JSON_spec_mark_optional ( 89 GNUNET_JSON_spec_object_copy ("jurisdiction", 90 &merchant->jurisdiction), 91 NULL), 92 GNUNET_JSON_spec_end () 93 }; 94 95 return GNUNET_JSON_parse (object, 96 spec, 97 NULL, 98 NULL); 99 } 100 101 102 /** 103 * Free parsed exchange candidates. 104 * 105 * @param num_candidates number of candidates 106 * @param candidates candidate array 107 */ 108 static void 109 free_exchange_candidates ( 110 unsigned int num_candidates, 111 struct TALER_MERCHANT_TemplateExchangeCandidate *candidates) 112 { 113 for (unsigned int i = 0; i<num_candidates; i++) 114 GNUNET_free (candidates[i].wire_methods); 115 GNUNET_free (candidates); 116 } 117 118 119 /** 120 * Parse exchange candidates from a template response. 121 * 122 * @param array candidate JSON array 123 * @param[out] num_candidates number of parsed entries 124 * @param[out] candidates parsed entries 125 * @return #GNUNET_OK on success 126 */ 127 static enum GNUNET_GenericReturnValue 128 parse_exchange_candidates ( 129 const json_t *array, 130 unsigned int *num_candidates, 131 struct TALER_MERCHANT_TemplateExchangeCandidate **candidates) 132 { 133 size_t array_size = json_array_size (array); 134 struct TALER_MERCHANT_TemplateExchangeCandidate *result; 135 136 if ( (array_size > MAX_CANDIDATE_ENTRIES) || 137 (array_size != (unsigned int) array_size) ) 138 return GNUNET_SYSERR; 139 result = GNUNET_new_array ( 140 array_size, 141 struct TALER_MERCHANT_TemplateExchangeCandidate); 142 for (unsigned int i = 0; i<array_size; i++) 143 { 144 struct TALER_MERCHANT_TemplateExchangeCandidate *candidate = &result[i]; 145 const json_t *wire_methods; 146 size_t num_methods; 147 const char **methods; 148 struct GNUNET_JSON_Specification spec[] = { 149 TALER_JSON_spec_web_url ("base_url", 150 &candidate->base_url), 151 GNUNET_JSON_spec_string ("currency", 152 &candidate->currency), 153 GNUNET_JSON_spec_fixed_auto ("master_pub", 154 &candidate->master_pub), 155 GNUNET_JSON_spec_array_const ("wire_methods", 156 &wire_methods), 157 GNUNET_JSON_spec_end () 158 }; 159 160 if (GNUNET_OK != 161 GNUNET_JSON_parse (json_array_get (array, 162 i), 163 spec, 164 NULL, 165 NULL)) 166 goto fail; 167 num_methods = json_array_size (wire_methods); 168 if ( (0 == num_methods) || 169 (num_methods > MAX_CANDIDATE_ENTRIES) || 170 (num_methods != (unsigned int) num_methods) ) 171 goto fail; 172 methods = GNUNET_new_array (num_methods, 173 const char *); 174 for (unsigned int j = 0; j<num_methods; j++) 175 { 176 const json_t *method = json_array_get (wire_methods, 177 j); 178 179 if (! json_is_string (method)) 180 { 181 GNUNET_free (methods); 182 goto fail; 183 } 184 methods[j] = json_string_value (method); 185 } 186 candidate->num_wire_methods = (unsigned int) num_methods; 187 candidate->wire_methods = methods; 188 } 189 *num_candidates = (unsigned int) array_size; 190 *candidates = result; 191 return GNUNET_OK; 192 193 fail: 194 free_exchange_candidates ((unsigned int) array_size, 195 result); 196 return GNUNET_SYSERR; 197 } 198 199 200 /** 201 * Handle for a GET /templates/$TEMPLATE_ID operation (wallet-facing). 202 */ 203 struct TALER_MERCHANT_GetTemplatesHandle 204 { 205 /** 206 * Base URL of the merchant backend. 207 */ 208 char *base_url; 209 210 /** 211 * The full URL for this request. 212 */ 213 char *url; 214 215 /** 216 * Handle for the request. 217 */ 218 struct GNUNET_CURL_Job *job; 219 220 /** 221 * Function to call with the result. 222 */ 223 TALER_MERCHANT_GetTemplatesCallback cb; 224 225 /** 226 * Closure for @a cb. 227 */ 228 TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE *cb_cls; 229 230 /** 231 * Reference to the execution context. 232 */ 233 struct GNUNET_CURL_Context *ctx; 234 235 /** 236 * Template identifier. 237 */ 238 char *template_id; 239 }; 240 241 242 /** 243 * Function called when we're done processing the 244 * HTTP GET /templates/$TEMPLATE_ID request. 245 * 246 * @param cls the `struct TALER_MERCHANT_GetTemplatesHandle` 247 * @param response_code HTTP response code, 0 on error 248 * @param response response body, NULL if not in JSON 249 */ 250 static void 251 handle_get_template_finished (void *cls, 252 long response_code, 253 const void *response) 254 { 255 struct TALER_MERCHANT_GetTemplatesHandle *gth = cls; 256 const json_t *json = response; 257 struct TALER_MERCHANT_MetaData merchant = { 0 }; 258 struct TALER_MerchantPublicKeyP merchant_pub; 259 struct TALER_MERCHANT_TemplateExchangeCandidate *candidates = NULL; 260 unsigned int num_candidates = 0; 261 struct TALER_MERCHANT_GetTemplatesResponse wtgr = { 262 .hr.http_status = (unsigned int) response_code, 263 .hr.reply = json 264 }; 265 266 gth->job = NULL; 267 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 268 "Got /templates/$TEMPLATE_ID response with status code %u\n", 269 (unsigned int) response_code); 270 switch (response_code) 271 { 272 case MHD_HTTP_OK: 273 { 274 const json_t *exchange_candidates = NULL; 275 const json_t *merchant_object = NULL; 276 bool exchange_candidates_missing; 277 bool merchant_missing; 278 bool merchant_pub_missing; 279 struct GNUNET_JSON_Specification spec[] = { 280 GNUNET_JSON_spec_object_const ("template_contract", 281 &wtgr.details.ok.template_contract), 282 GNUNET_JSON_spec_mark_optional ( 283 GNUNET_JSON_spec_object_const ( 284 "editable_defaults", 285 &wtgr.details.ok.editable_defaults), 286 NULL), 287 GNUNET_JSON_spec_mark_optional ( 288 GNUNET_JSON_spec_string ( 289 "required_currency", 290 &wtgr.details.ok.required_currency), 291 NULL), 292 GNUNET_JSON_spec_mark_optional ( 293 GNUNET_JSON_spec_object_const ("merchant", 294 &merchant_object), 295 &merchant_missing), 296 GNUNET_JSON_spec_mark_optional ( 297 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 298 &merchant_pub), 299 &merchant_pub_missing), 300 GNUNET_JSON_spec_mark_optional ( 301 GNUNET_JSON_spec_array_const ("exchange_candidates", 302 &exchange_candidates), 303 &exchange_candidates_missing), 304 GNUNET_JSON_spec_end () 305 }; 306 307 if (GNUNET_OK != 308 GNUNET_JSON_parse (json, 309 spec, 310 NULL, NULL)) 311 { 312 wtgr.hr.http_status = 0; 313 wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 314 break; 315 } 316 { 317 if (merchant_missing != merchant_pub_missing) 318 { 319 wtgr.hr.http_status = 0; 320 wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 321 break; 322 } 323 if (! merchant_missing) 324 { 325 if (GNUNET_OK != 326 parse_merchant_metadata (merchant_object, 327 &merchant)) 328 { 329 wtgr.hr.http_status = 0; 330 wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 331 break; 332 } 333 wtgr.details.ok.merchant = &merchant; 334 wtgr.details.ok.merchant_pub = &merchant_pub; 335 } 336 } 337 if (! exchange_candidates_missing) 338 { 339 if (GNUNET_OK != 340 parse_exchange_candidates (exchange_candidates, 341 &num_candidates, 342 &candidates)) 343 { 344 wtgr.hr.http_status = 0; 345 wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 346 break; 347 } 348 wtgr.details.ok.exchange_candidates_provided = true; 349 wtgr.details.ok.num_exchange_candidates = num_candidates; 350 wtgr.details.ok.exchange_candidates = candidates; 351 } 352 break; 353 } 354 case MHD_HTTP_UNAUTHORIZED: 355 wtgr.hr.ec = TALER_JSON_get_error_code (json); 356 wtgr.hr.hint = TALER_JSON_get_error_hint (json); 357 break; 358 case MHD_HTTP_NOT_FOUND: 359 wtgr.hr.ec = TALER_JSON_get_error_code (json); 360 wtgr.hr.hint = TALER_JSON_get_error_hint (json); 361 break; 362 default: 363 wtgr.hr.ec = TALER_JSON_get_error_code (json); 364 wtgr.hr.hint = TALER_JSON_get_error_hint (json); 365 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 366 "Unexpected response code %u/%d\n", 367 (unsigned int) response_code, 368 (int) wtgr.hr.ec); 369 break; 370 } 371 gth->cb (gth->cb_cls, 372 &wtgr); 373 free_exchange_candidates (num_candidates, 374 candidates); 375 free_merchant_metadata (&merchant); 376 TALER_MERCHANT_get_templates_cancel (gth); 377 } 378 379 380 struct TALER_MERCHANT_GetTemplatesHandle * 381 TALER_MERCHANT_get_templates_create ( 382 struct GNUNET_CURL_Context *ctx, 383 const char *url, 384 const char *template_id) 385 { 386 struct TALER_MERCHANT_GetTemplatesHandle *gth; 387 388 gth = GNUNET_new (struct TALER_MERCHANT_GetTemplatesHandle); 389 gth->ctx = ctx; 390 gth->base_url = GNUNET_strdup (url); 391 gth->template_id = GNUNET_strdup (template_id); 392 return gth; 393 } 394 395 396 enum TALER_ErrorCode 397 TALER_MERCHANT_get_templates_start ( 398 struct TALER_MERCHANT_GetTemplatesHandle *gth, 399 TALER_MERCHANT_GetTemplatesCallback cb, 400 TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE *cb_cls) 401 { 402 CURL *eh; 403 404 gth->cb = cb; 405 gth->cb_cls = cb_cls; 406 { 407 char *path; 408 409 GNUNET_asprintf (&path, 410 "templates/%s", 411 gth->template_id); 412 gth->url = TALER_url_join (gth->base_url, 413 path, 414 NULL); 415 GNUNET_free (path); 416 } 417 if (NULL == gth->url) 418 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 419 eh = TALER_MERCHANT_curl_easy_get_ (gth->url); 420 if (NULL == eh) 421 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 422 gth->job = GNUNET_CURL_job_add (gth->ctx, 423 eh, 424 &handle_get_template_finished, 425 gth); 426 if (NULL == gth->job) 427 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 428 return TALER_EC_NONE; 429 } 430 431 432 void 433 TALER_MERCHANT_get_templates_cancel ( 434 struct TALER_MERCHANT_GetTemplatesHandle *gth) 435 { 436 if (NULL != gth->job) 437 { 438 GNUNET_CURL_job_cancel (gth->job); 439 gth->job = NULL; 440 } 441 GNUNET_free (gth->url); 442 GNUNET_free (gth->base_url); 443 GNUNET_free (gth->template_id); 444 GNUNET_free (gth); 445 } 446 447 448 /* end of merchant_api_get-templates-TEMPLATE_ID-new.c */