Line data Source code
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 6 : free_merchant_metadata (struct TALER_MERCHANT_MetaData *merchant)
46 : {
47 6 : GNUNET_free (merchant->name);
48 6 : GNUNET_free (merchant->website);
49 6 : GNUNET_free (merchant->email);
50 6 : GNUNET_free (merchant->logo);
51 6 : if (NULL != merchant->address)
52 6 : json_decref (merchant->address);
53 6 : if (NULL != merchant->jurisdiction)
54 6 : json_decref (merchant->jurisdiction);
55 6 : }
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 6 : parse_merchant_metadata (const json_t *object,
67 : struct TALER_MERCHANT_MetaData *merchant)
68 : {
69 : struct GNUNET_JSON_Specification spec[] = {
70 6 : GNUNET_JSON_spec_string_copy ("name",
71 : &merchant->name),
72 6 : GNUNET_JSON_spec_mark_optional (
73 : TALER_JSON_spec_web_url_copy ("website",
74 : &merchant->website),
75 : NULL),
76 6 : GNUNET_JSON_spec_mark_optional (
77 : GNUNET_JSON_spec_string_copy ("email",
78 : &merchant->email),
79 : NULL),
80 6 : GNUNET_JSON_spec_mark_optional (
81 : GNUNET_JSON_spec_string_copy ("logo",
82 : &merchant->logo),
83 : NULL),
84 6 : GNUNET_JSON_spec_mark_optional (
85 : GNUNET_JSON_spec_object_copy ("address",
86 : &merchant->address),
87 : NULL),
88 6 : GNUNET_JSON_spec_mark_optional (
89 : GNUNET_JSON_spec_object_copy ("jurisdiction",
90 : &merchant->jurisdiction),
91 : NULL),
92 6 : GNUNET_JSON_spec_end ()
93 : };
94 :
95 6 : 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 6 : free_exchange_candidates (
110 : unsigned int num_candidates,
111 : struct TALER_MERCHANT_TemplateExchangeCandidate *candidates)
112 : {
113 12 : for (unsigned int i = 0; i<num_candidates; i++)
114 6 : GNUNET_free (candidates[i].wire_methods);
115 6 : GNUNET_free (candidates);
116 6 : }
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 6 : parse_exchange_candidates (
129 : const json_t *array,
130 : unsigned int *num_candidates,
131 : struct TALER_MERCHANT_TemplateExchangeCandidate **candidates)
132 : {
133 6 : size_t array_size = json_array_size (array);
134 : struct TALER_MERCHANT_TemplateExchangeCandidate *result;
135 :
136 6 : if ( (array_size > MAX_CANDIDATE_ENTRIES) ||
137 6 : (array_size != (unsigned int) array_size) )
138 0 : return GNUNET_SYSERR;
139 6 : result = GNUNET_new_array (
140 : array_size,
141 : struct TALER_MERCHANT_TemplateExchangeCandidate);
142 12 : for (unsigned int i = 0; i<array_size; i++)
143 : {
144 6 : 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 6 : TALER_JSON_spec_web_url ("base_url",
150 : &candidate->base_url),
151 6 : GNUNET_JSON_spec_string ("currency",
152 : &candidate->currency),
153 6 : GNUNET_JSON_spec_fixed_auto ("master_pub",
154 : &candidate->master_pub),
155 6 : GNUNET_JSON_spec_array_const ("wire_methods",
156 : &wire_methods),
157 6 : GNUNET_JSON_spec_end ()
158 : };
159 :
160 6 : if (GNUNET_OK !=
161 6 : GNUNET_JSON_parse (json_array_get (array,
162 : i),
163 : spec,
164 : NULL,
165 : NULL))
166 0 : goto fail;
167 6 : num_methods = json_array_size (wire_methods);
168 6 : if ( (0 == num_methods) ||
169 6 : (num_methods > MAX_CANDIDATE_ENTRIES) ||
170 6 : (num_methods != (unsigned int) num_methods) )
171 0 : goto fail;
172 6 : methods = GNUNET_new_array (num_methods,
173 : const char *);
174 12 : for (unsigned int j = 0; j<num_methods; j++)
175 : {
176 6 : const json_t *method = json_array_get (wire_methods,
177 : j);
178 :
179 6 : if (! json_is_string (method))
180 : {
181 0 : GNUNET_free (methods);
182 0 : goto fail;
183 : }
184 6 : methods[j] = json_string_value (method);
185 : }
186 6 : candidate->num_wire_methods = (unsigned int) num_methods;
187 6 : candidate->wire_methods = methods;
188 : }
189 6 : *num_candidates = (unsigned int) array_size;
190 6 : *candidates = result;
191 6 : return GNUNET_OK;
192 :
193 0 : fail:
194 0 : free_exchange_candidates ((unsigned int) array_size,
195 : result);
196 0 : 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 6 : handle_get_template_finished (void *cls,
252 : long response_code,
253 : const void *response)
254 : {
255 6 : struct TALER_MERCHANT_GetTemplatesHandle *gth = cls;
256 6 : const json_t *json = response;
257 6 : struct TALER_MERCHANT_MetaData merchant = { 0 };
258 : struct TALER_MerchantPublicKeyP merchant_pub;
259 6 : struct TALER_MERCHANT_TemplateExchangeCandidate *candidates = NULL;
260 6 : unsigned int num_candidates = 0;
261 6 : struct TALER_MERCHANT_GetTemplatesResponse wtgr = {
262 6 : .hr.http_status = (unsigned int) response_code,
263 : .hr.reply = json
264 : };
265 :
266 6 : gth->job = NULL;
267 6 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
268 : "Got /templates/$TEMPLATE_ID response with status code %u\n",
269 : (unsigned int) response_code);
270 6 : switch (response_code)
271 : {
272 6 : case MHD_HTTP_OK:
273 : {
274 6 : const json_t *exchange_candidates = NULL;
275 6 : 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 6 : GNUNET_JSON_spec_object_const ("template_contract",
281 : &wtgr.details.ok.template_contract),
282 6 : GNUNET_JSON_spec_mark_optional (
283 : GNUNET_JSON_spec_object_const (
284 : "editable_defaults",
285 : &wtgr.details.ok.editable_defaults),
286 : NULL),
287 6 : GNUNET_JSON_spec_mark_optional (
288 : GNUNET_JSON_spec_string (
289 : "required_currency",
290 : &wtgr.details.ok.required_currency),
291 : NULL),
292 6 : GNUNET_JSON_spec_mark_optional (
293 : GNUNET_JSON_spec_object_const ("merchant",
294 : &merchant_object),
295 : &merchant_missing),
296 6 : GNUNET_JSON_spec_mark_optional (
297 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
298 : &merchant_pub),
299 : &merchant_pub_missing),
300 6 : GNUNET_JSON_spec_mark_optional (
301 : GNUNET_JSON_spec_array_const ("exchange_candidates",
302 : &exchange_candidates),
303 : &exchange_candidates_missing),
304 6 : GNUNET_JSON_spec_end ()
305 : };
306 :
307 6 : if (GNUNET_OK !=
308 6 : GNUNET_JSON_parse (json,
309 : spec,
310 : NULL, NULL))
311 : {
312 0 : wtgr.hr.http_status = 0;
313 0 : wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
314 0 : break;
315 : }
316 : {
317 6 : if (merchant_missing != merchant_pub_missing)
318 : {
319 0 : wtgr.hr.http_status = 0;
320 0 : wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
321 0 : break;
322 : }
323 6 : if (! merchant_missing)
324 : {
325 6 : if (GNUNET_OK !=
326 6 : parse_merchant_metadata (merchant_object,
327 : &merchant))
328 : {
329 0 : wtgr.hr.http_status = 0;
330 0 : wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
331 0 : break;
332 : }
333 6 : wtgr.details.ok.merchant = &merchant;
334 6 : wtgr.details.ok.merchant_pub = &merchant_pub;
335 : }
336 : }
337 6 : if (! exchange_candidates_missing)
338 : {
339 6 : if (GNUNET_OK !=
340 6 : parse_exchange_candidates (exchange_candidates,
341 : &num_candidates,
342 : &candidates))
343 : {
344 0 : wtgr.hr.http_status = 0;
345 0 : wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
346 0 : break;
347 : }
348 6 : wtgr.details.ok.exchange_candidates_provided = true;
349 6 : wtgr.details.ok.num_exchange_candidates = num_candidates;
350 6 : wtgr.details.ok.exchange_candidates = candidates;
351 : }
352 6 : break;
353 : }
354 0 : case MHD_HTTP_UNAUTHORIZED:
355 0 : wtgr.hr.ec = TALER_JSON_get_error_code (json);
356 0 : wtgr.hr.hint = TALER_JSON_get_error_hint (json);
357 0 : break;
358 0 : case MHD_HTTP_NOT_FOUND:
359 0 : wtgr.hr.ec = TALER_JSON_get_error_code (json);
360 0 : wtgr.hr.hint = TALER_JSON_get_error_hint (json);
361 0 : break;
362 0 : default:
363 0 : wtgr.hr.ec = TALER_JSON_get_error_code (json);
364 0 : wtgr.hr.hint = TALER_JSON_get_error_hint (json);
365 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
366 : "Unexpected response code %u/%d\n",
367 : (unsigned int) response_code,
368 : (int) wtgr.hr.ec);
369 0 : break;
370 : }
371 6 : gth->cb (gth->cb_cls,
372 : &wtgr);
373 6 : free_exchange_candidates (num_candidates,
374 : candidates);
375 6 : free_merchant_metadata (&merchant);
376 6 : TALER_MERCHANT_get_templates_cancel (gth);
377 6 : }
378 :
379 :
380 : struct TALER_MERCHANT_GetTemplatesHandle *
381 6 : 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 6 : gth = GNUNET_new (struct TALER_MERCHANT_GetTemplatesHandle);
389 6 : gth->ctx = ctx;
390 6 : gth->base_url = GNUNET_strdup (url);
391 6 : gth->template_id = GNUNET_strdup (template_id);
392 6 : return gth;
393 : }
394 :
395 :
396 : enum TALER_ErrorCode
397 6 : 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 6 : gth->cb = cb;
405 6 : gth->cb_cls = cb_cls;
406 : {
407 : char *path;
408 :
409 6 : GNUNET_asprintf (&path,
410 : "templates/%s",
411 : gth->template_id);
412 6 : gth->url = TALER_url_join (gth->base_url,
413 : path,
414 : NULL);
415 6 : GNUNET_free (path);
416 : }
417 6 : if (NULL == gth->url)
418 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
419 6 : eh = TALER_MERCHANT_curl_easy_get_ (gth->url);
420 6 : if (NULL == eh)
421 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
422 6 : gth->job = GNUNET_CURL_job_add (gth->ctx,
423 : eh,
424 : &handle_get_template_finished,
425 : gth);
426 6 : if (NULL == gth->job)
427 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
428 6 : return TALER_EC_NONE;
429 : }
430 :
431 :
432 : void
433 6 : TALER_MERCHANT_get_templates_cancel (
434 : struct TALER_MERCHANT_GetTemplatesHandle *gth)
435 : {
436 6 : if (NULL != gth->job)
437 : {
438 0 : GNUNET_CURL_job_cancel (gth->job);
439 0 : gth->job = NULL;
440 : }
441 6 : GNUNET_free (gth->url);
442 6 : GNUNET_free (gth->base_url);
443 6 : GNUNET_free (gth->template_id);
444 6 : GNUNET_free (gth);
445 6 : }
446 :
447 :
448 : /* end of merchant_api_get-templates-TEMPLATE_ID-new.c */
|