Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2024 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.c
19 : * @brief Implementation of the /config request of the merchant's HTTP API
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_service.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 19
38 :
39 : /**
40 : * How many configs are we backwards-compatible with?
41 : */
42 : #define MERCHANT_PROTOCOL_AGE 7
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 : * @brief A handle for /config operations
56 : */
57 : struct TALER_MERCHANT_ConfigGetHandle
58 : {
59 : /**
60 : * The url for this request.
61 : */
62 : char *url;
63 :
64 : /**
65 : * Handle for the request.
66 : */
67 : struct GNUNET_CURL_Job *job;
68 :
69 : /**
70 : * Function to call with the result.
71 : */
72 : TALER_MERCHANT_ConfigCallback cb;
73 :
74 : /**
75 : * Closure for @a cb.
76 : */
77 : void *cb_cls;
78 :
79 : /**
80 : * Reference to the execution context.
81 : */
82 : struct GNUNET_CURL_Context *ctx;
83 :
84 : };
85 :
86 :
87 : /**
88 : * Function called when we're done processing the
89 : * HTTP /config request.
90 : *
91 : * @param cls the `struct TALER_MERCHANT_ConfigGetHandle`
92 : * @param response_code HTTP response code, 0 on error
93 : * @param response response body, NULL if not in JSON
94 : */
95 : static void
96 2 : handle_config_finished (void *cls,
97 : long response_code,
98 : const void *response)
99 : {
100 2 : struct TALER_MERCHANT_ConfigGetHandle *vgh = cls;
101 2 : const json_t *json = response;
102 2 : struct TALER_MERCHANT_ConfigResponse cr = {
103 2 : .hr.http_status = (unsigned int) response_code,
104 : .hr.reply = json
105 : };
106 :
107 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
108 : "Got /config response with status code %u\n",
109 : (unsigned int) response_code);
110 :
111 2 : vgh->job = NULL;
112 2 : switch (response_code)
113 : {
114 2 : case MHD_HTTP_OK:
115 : {
116 : const json_t *jcs;
117 2 : const json_t *exchanges = NULL;
118 2 : struct TALER_MERCHANT_ExchangeConfigInfo *eci = NULL;
119 2 : unsigned int num_eci = 0;
120 : unsigned int nspec;
121 : struct TALER_JSON_ProtocolVersion pv;
122 : struct GNUNET_JSON_Specification spec[] = {
123 2 : GNUNET_JSON_spec_object_const ("currencies",
124 : &jcs),
125 2 : GNUNET_JSON_spec_array_const ("exchanges",
126 : &exchanges),
127 2 : GNUNET_JSON_spec_string ("currency",
128 : &cr.details.ok.ci.currency),
129 2 : TALER_JSON_spec_version ("version",
130 : &pv),
131 2 : GNUNET_JSON_spec_string ("version",
132 : &cr.details.ok.ci.version),
133 2 : GNUNET_JSON_spec_end ()
134 : };
135 :
136 2 : cr.details.ok.compat = TALER_MERCHANT_VC_PROTOCOL_ERROR;
137 2 : if (GNUNET_OK !=
138 2 : GNUNET_JSON_parse (json,
139 : spec,
140 : NULL, NULL))
141 : {
142 0 : GNUNET_break_op (0);
143 0 : cr.hr.http_status = 0;
144 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
145 0 : break;
146 : }
147 2 : cr.details.ok.compat = TALER_MERCHANT_VC_MATCH;
148 2 : if (MERCHANT_PROTOCOL_CURRENT < pv.current)
149 : {
150 0 : cr.details.ok.compat |= TALER_MERCHANT_VC_NEWER;
151 0 : if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age)
152 0 : cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE;
153 : }
154 2 : if (MERCHANT_PROTOCOL_CURRENT > pv.current)
155 : {
156 0 : cr.details.ok.compat |= TALER_MERCHANT_VC_OLDER;
157 0 : if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current)
158 0 : cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE;
159 : }
160 :
161 2 : nspec = (unsigned int) json_object_size (jcs);
162 2 : if ( (nspec > MAX_CURRENCIES) ||
163 2 : (json_object_size (jcs) != (size_t) nspec) )
164 : {
165 0 : GNUNET_break_op (0);
166 0 : cr.hr.http_status = 0;
167 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
168 0 : break;
169 : }
170 2 : if (NULL != exchanges)
171 : {
172 2 : num_eci = (unsigned int) json_object_size (exchanges);
173 2 : if ( (num_eci > MAX_EXCHANGES) ||
174 2 : (json_object_size (exchanges) != (size_t) num_eci) )
175 : {
176 0 : GNUNET_break_op (0);
177 0 : cr.hr.http_status = 0;
178 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
179 0 : break;
180 : }
181 2 : eci = GNUNET_new_array (num_eci,
182 : struct TALER_MERCHANT_ExchangeConfigInfo);
183 2 : for (unsigned int i = 0; i<num_eci; i++)
184 : {
185 0 : struct TALER_MERCHANT_ExchangeConfigInfo *ei = &eci[i];
186 0 : const json_t *ej = json_array_get (exchanges,
187 : i);
188 : struct GNUNET_JSON_Specification ispec[] = {
189 0 : GNUNET_JSON_spec_string ("currency",
190 : &ei->currency),
191 0 : GNUNET_JSON_spec_string ("base_url",
192 : &ei->base_url),
193 0 : GNUNET_JSON_spec_fixed_auto ("master_pub",
194 : &ei->master_pub),
195 0 : GNUNET_JSON_spec_end ()
196 : };
197 :
198 0 : if (GNUNET_OK !=
199 0 : GNUNET_JSON_parse (ej,
200 : ispec,
201 : NULL, NULL))
202 : {
203 0 : GNUNET_break_op (0);
204 0 : cr.hr.http_status = 0;
205 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
206 0 : GNUNET_free (eci);
207 0 : break;
208 : }
209 : }
210 : }
211 : {
212 : struct TALER_CurrencySpecification *cspecs;
213 2 : unsigned int off = 0;
214 : json_t *obj;
215 : const char *curr;
216 :
217 2 : cspecs = GNUNET_new_array (nspec,
218 : struct TALER_CurrencySpecification);
219 2 : cr.details.ok.num_cspecs = nspec;
220 2 : cr.details.ok.cspecs = cspecs;
221 2 : cr.details.ok.num_exchanges = (unsigned int) num_eci;
222 2 : cr.details.ok.exchanges = eci;
223 6 : json_object_foreach ((json_t *) jcs, curr, obj)
224 : {
225 4 : struct TALER_CurrencySpecification *cs = &cspecs[off++];
226 : struct GNUNET_JSON_Specification cspec[] = {
227 4 : TALER_JSON_spec_currency_specification (curr,
228 : curr,
229 : cs),
230 4 : GNUNET_JSON_spec_end ()
231 : };
232 :
233 4 : if (GNUNET_OK !=
234 4 : GNUNET_JSON_parse (jcs,
235 : cspec,
236 : NULL, NULL))
237 : {
238 0 : GNUNET_break_op (0);
239 0 : cr.hr.http_status = 0;
240 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
241 0 : GNUNET_free (eci);
242 0 : TALER_CONFIG_free_currencies (off - 1,
243 : cspecs);
244 0 : break;
245 : }
246 : }
247 2 : vgh->cb (vgh->cb_cls,
248 : &cr);
249 2 : GNUNET_free (eci);
250 2 : TALER_CONFIG_free_currencies (nspec,
251 : cspecs);
252 : }
253 2 : TALER_MERCHANT_config_get_cancel (vgh);
254 2 : return;
255 : }
256 0 : default:
257 : /* unexpected response code */
258 0 : cr.hr.ec = TALER_JSON_get_error_code (json);
259 0 : cr.hr.hint = TALER_JSON_get_error_hint (json);
260 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
261 : "Unexpected response code %u/%d\n",
262 : (unsigned int) response_code,
263 : (int) cr.hr.ec);
264 0 : break;
265 : }
266 0 : vgh->cb (vgh->cb_cls,
267 : &cr);
268 0 : TALER_MERCHANT_config_get_cancel (vgh);
269 : }
270 :
271 :
272 : struct TALER_MERCHANT_ConfigGetHandle *
273 2 : TALER_MERCHANT_config_get (struct GNUNET_CURL_Context *ctx,
274 : const char *backend_url,
275 : TALER_MERCHANT_ConfigCallback config_cb,
276 : void *config_cb_cls)
277 : {
278 : struct TALER_MERCHANT_ConfigGetHandle *vgh;
279 : CURL *eh;
280 :
281 2 : vgh = GNUNET_new (struct TALER_MERCHANT_ConfigGetHandle);
282 2 : vgh->ctx = ctx;
283 2 : vgh->cb = config_cb;
284 2 : vgh->cb_cls = config_cb_cls;
285 2 : vgh->url = TALER_url_join (backend_url,
286 : "config",
287 : NULL);
288 2 : if (NULL == vgh->url)
289 : {
290 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
291 : "Could not construct request URL.\n");
292 0 : GNUNET_free (vgh);
293 0 : return NULL;
294 : }
295 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
296 : "Requesting URL '%s'\n",
297 : vgh->url);
298 2 : eh = TALER_MERCHANT_curl_easy_get_ (vgh->url);
299 2 : vgh->job = GNUNET_CURL_job_add (ctx,
300 : eh,
301 : &handle_config_finished,
302 : vgh);
303 2 : return vgh;
304 : }
305 :
306 :
307 : void
308 2 : TALER_MERCHANT_config_get_cancel (struct TALER_MERCHANT_ConfigGetHandle *vgh)
309 : {
310 2 : if (NULL != vgh->job)
311 : {
312 0 : GNUNET_CURL_job_cancel (vgh->job);
313 0 : vgh->job = NULL;
314 : }
315 2 : GNUNET_free (vgh->url);
316 2 : GNUNET_free (vgh);
317 2 : }
318 :
319 :
320 : /* end of merchant_api_config_get.c */
|