merchant_api_get-config.c (12121B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-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 merchant_api_get-config-new.c 19 * @brief Implementation of the GET /config request 20 * @author Christian Grothoff 21 */ 22 #include "taler/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/taler-merchant/get-config.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 #include <taler/taler_signatures.h> 32 33 /** 34 * Which version of the Taler protocol is implemented 35 * by this library? Used to determine compatibility. 36 */ 37 #define MERCHANT_PROTOCOL_CURRENT 27 38 39 /** 40 * How many configs are we backwards-compatible with? 41 */ 42 #define MERCHANT_PROTOCOL_AGE 3 43 44 /** 45 * How many exchanges do we allow at most per merchant? 46 */ 47 #define MAX_EXCHANGES 1024 48 49 /** 50 * How many currency specs do we allow at most per merchant? 51 */ 52 #define MAX_CURRENCIES 1024 53 54 55 /** 56 * Handle for a GET /config operation. 57 */ 58 struct TALER_MERCHANT_GetConfigHandle 59 { 60 /** 61 * Base URL of the merchant backend. 62 */ 63 char *base_url; 64 65 /** 66 * The full URL for this request. 67 */ 68 char *url; 69 70 /** 71 * Handle for the request. 72 */ 73 struct GNUNET_CURL_Job *job; 74 75 /** 76 * Function to call with the result. 77 */ 78 TALER_MERCHANT_GetConfigCallback cb; 79 80 /** 81 * Closure for @a cb. 82 */ 83 TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls; 84 85 /** 86 * Reference to the execution context. 87 */ 88 struct GNUNET_CURL_Context *ctx; 89 }; 90 91 92 /** 93 * Function called when we're done processing the 94 * HTTP GET /config request. 95 * 96 * @param cls the `struct TALER_MERCHANT_GetConfigHandle` 97 * @param response_code HTTP response code, 0 on error 98 * @param response response body, NULL if not in JSON 99 */ 100 static void 101 handle_get_config_finished (void *cls, 102 long response_code, 103 const void *response) 104 { 105 struct TALER_MERCHANT_GetConfigHandle *gch = cls; 106 const json_t *json = response; 107 struct TALER_MERCHANT_GetConfigResponse cr = { 108 .hr.http_status = (unsigned int) response_code, 109 .hr.reply = json 110 }; 111 112 gch->job = NULL; 113 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 114 "Got /config response with status code %u\n", 115 (unsigned int) response_code); 116 switch (response_code) 117 { 118 case MHD_HTTP_OK: 119 { 120 const json_t *jcs; 121 const json_t *exchanges = NULL; 122 struct TALER_MERCHANT_GetConfigExchangeInfo *eci = NULL; 123 unsigned int num_eci = 0; 124 unsigned int nspec; 125 struct TALER_JSON_ProtocolVersion pv; 126 struct GNUNET_JSON_Specification spec[] = { 127 GNUNET_JSON_spec_object_const ("currencies", 128 &jcs), 129 GNUNET_JSON_spec_array_const ("exchanges", 130 &exchanges), 131 GNUNET_JSON_spec_string ("currency", 132 &cr.details.ok.ci.currency), 133 TALER_JSON_spec_version ("version", 134 &pv), 135 GNUNET_JSON_spec_string ("version", 136 &cr.details.ok.ci.version), 137 GNUNET_JSON_spec_mark_optional ( 138 GNUNET_JSON_spec_string ("implementation", 139 &cr.details.ok.implementation), 140 NULL), 141 GNUNET_JSON_spec_bool ("have_self_provisioning", 142 &cr.details.ok.have_self_provisioning), 143 GNUNET_JSON_spec_bool ("have_donau", 144 &cr.details.ok.have_donau), 145 GNUNET_JSON_spec_string ("payment_target_types", 146 &cr.details.ok.payment_target_types), 147 GNUNET_JSON_spec_mark_optional ( 148 GNUNET_JSON_spec_string ("payment_target_regex", 149 &cr.details.ok.payment_target_regex), 150 NULL), 151 GNUNET_JSON_spec_mark_optional ( 152 GNUNET_JSON_spec_array_const ("mandatory_tan_channels", 153 &cr.details.ok.mandatory_tan_channels), 154 NULL), 155 GNUNET_JSON_spec_relative_time ("default_pay_delay", 156 &cr.details.ok.default_pay_delay), 157 GNUNET_JSON_spec_relative_time ("default_refund_delay", 158 &cr.details.ok.default_refund_delay), 159 GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", 160 &cr.details.ok.default_wire_transfer_delay), 161 GNUNET_JSON_spec_string ("default_persona", 162 &cr.details.ok.default_persona), 163 GNUNET_JSON_spec_mark_optional ( 164 GNUNET_JSON_spec_array_const ("report_generators", 165 &cr.details.ok.report_generators), 166 NULL), 167 GNUNET_JSON_spec_mark_optional ( 168 GNUNET_JSON_spec_string ("phone_regex", 169 &cr.details.ok.phone_regex), 170 NULL), 171 GNUNET_JSON_spec_mark_optional ( 172 GNUNET_JSON_spec_object_const ("spa_options", 173 &cr.details.ok.spa_options), 174 NULL), 175 GNUNET_JSON_spec_end () 176 }; 177 178 cr.details.ok.compat 179 = TALER_MERCHANT_GET_CONFIG_VC_PROTOCOL_ERROR; 180 if (GNUNET_OK != 181 GNUNET_JSON_parse (json, 182 spec, 183 NULL, NULL)) 184 { 185 GNUNET_break_op (0); 186 cr.hr.http_status = 0; 187 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 188 break; 189 } 190 cr.details.ok.compat = TALER_MERCHANT_GET_CONFIG_VC_MATCH; 191 if (MERCHANT_PROTOCOL_CURRENT < pv.current) 192 { 193 cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_NEWER; 194 if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age) 195 cr.details.ok.compat 196 |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE; 197 } 198 if (MERCHANT_PROTOCOL_CURRENT > pv.current) 199 { 200 cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_OLDER; 201 if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current) 202 cr.details.ok.compat 203 |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE; 204 } 205 206 nspec = (unsigned int) json_object_size (jcs); 207 if ( (nspec > MAX_CURRENCIES) || 208 (json_object_size (jcs) != (size_t) nspec) ) 209 { 210 GNUNET_break_op (0); 211 cr.hr.http_status = 0; 212 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 213 break; 214 } 215 if (NULL != exchanges) 216 { 217 num_eci = (unsigned int) json_array_size (exchanges); 218 if ( (num_eci > MAX_EXCHANGES) || 219 (json_array_size (exchanges) != (size_t) num_eci) ) 220 { 221 GNUNET_break_op (0); 222 cr.hr.http_status = 0; 223 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 224 break; 225 } 226 eci = GNUNET_new_array (num_eci, 227 struct TALER_MERCHANT_GetConfigExchangeInfo); 228 for (unsigned int i = 0; i<num_eci; i++) 229 { 230 struct TALER_MERCHANT_GetConfigExchangeInfo *ei = &eci[i]; 231 const json_t *ej = json_array_get (exchanges, 232 i); 233 struct GNUNET_JSON_Specification ispec[] = { 234 GNUNET_JSON_spec_string ("currency", 235 &ei->currency), 236 GNUNET_JSON_spec_string ("base_url", 237 &ei->base_url), 238 GNUNET_JSON_spec_fixed_auto ("master_pub", 239 &ei->master_pub), 240 GNUNET_JSON_spec_end () 241 }; 242 243 if (GNUNET_OK != 244 GNUNET_JSON_parse (ej, 245 ispec, 246 NULL, NULL)) 247 { 248 GNUNET_break_op (0); 249 cr.hr.http_status = 0; 250 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 251 GNUNET_free (eci); 252 break; 253 } 254 } 255 } 256 { 257 struct TALER_CurrencySpecification *cspecs; 258 unsigned int off = 0; 259 json_t *obj; 260 const char *curr; 261 262 cspecs = GNUNET_new_array (nspec, 263 struct TALER_CurrencySpecification); 264 cr.details.ok.num_cspecs = nspec; 265 cr.details.ok.cspecs = cspecs; 266 cr.details.ok.num_exchanges = (unsigned int) num_eci; 267 cr.details.ok.exchanges = eci; 268 json_object_foreach ((json_t *) jcs, curr, obj) 269 { 270 struct TALER_CurrencySpecification *cs = &cspecs[off++]; 271 struct GNUNET_JSON_Specification cspec[] = { 272 TALER_JSON_spec_currency_specification (curr, 273 curr, 274 cs), 275 GNUNET_JSON_spec_end () 276 }; 277 278 if (GNUNET_OK != 279 GNUNET_JSON_parse (jcs, 280 cspec, 281 NULL, NULL)) 282 { 283 GNUNET_break_op (0); 284 cr.hr.http_status = 0; 285 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 286 GNUNET_free (eci); 287 TALER_CONFIG_free_currencies (off - 1, 288 cspecs); 289 break; 290 } 291 } 292 gch->cb (gch->cb_cls, 293 &cr); 294 GNUNET_free (eci); 295 TALER_CONFIG_free_currencies (nspec, 296 cspecs); 297 } 298 TALER_MERCHANT_get_config_cancel (gch); 299 return; 300 } 301 default: 302 cr.hr.ec = TALER_JSON_get_error_code (json); 303 cr.hr.hint = TALER_JSON_get_error_hint (json); 304 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 305 "Unexpected response code %u/%d\n", 306 (unsigned int) response_code, 307 (int) cr.hr.ec); 308 break; 309 } 310 gch->cb (gch->cb_cls, 311 &cr); 312 TALER_MERCHANT_get_config_cancel (gch); 313 } 314 315 316 struct TALER_MERCHANT_GetConfigHandle * 317 TALER_MERCHANT_get_config_create ( 318 struct GNUNET_CURL_Context *ctx, 319 const char *url) 320 { 321 struct TALER_MERCHANT_GetConfigHandle *gch; 322 323 gch = GNUNET_new (struct TALER_MERCHANT_GetConfigHandle); 324 gch->ctx = ctx; 325 gch->base_url = GNUNET_strdup (url); 326 return gch; 327 } 328 329 330 enum TALER_ErrorCode 331 TALER_MERCHANT_get_config_start ( 332 struct TALER_MERCHANT_GetConfigHandle *gch, 333 TALER_MERCHANT_GetConfigCallback cb, 334 TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls) 335 { 336 CURL *eh; 337 338 gch->cb = cb; 339 gch->cb_cls = cb_cls; 340 gch->url = TALER_url_join (gch->base_url, 341 "config", 342 NULL); 343 if (NULL == gch->url) 344 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 345 eh = TALER_MERCHANT_curl_easy_get_ (gch->url); 346 if (NULL == eh) 347 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 348 gch->job = GNUNET_CURL_job_add (gch->ctx, 349 eh, 350 &handle_get_config_finished, 351 gch); 352 if (NULL == gch->job) 353 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 354 return TALER_EC_NONE; 355 } 356 357 358 void 359 TALER_MERCHANT_get_config_cancel ( 360 struct TALER_MERCHANT_GetConfigHandle *gch) 361 { 362 if (NULL != gch->job) 363 { 364 GNUNET_CURL_job_cancel (gch->job); 365 gch->job = NULL; 366 } 367 GNUNET_free (gch->url); 368 GNUNET_free (gch->base_url); 369 GNUNET_free (gch); 370 } 371 372 373 /* end of merchant_api_get-config-new.c */