Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020-2023 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_common.c
19 : * @brief Implementation of common logic for libtalermerchant
20 : * @author Christian Grothoff
21 : * @author Priscilla Huang
22 : */
23 : #include "platform.h"
24 : #include "microhttpd.h"
25 : #include <curl/curl.h>
26 : #include "taler_merchant_service.h"
27 : #include "merchant_api_common.h"
28 : #include <gnunet/gnunet_uri_lib.h>
29 : #include <taler/taler_json_lib.h>
30 :
31 :
32 : void
33 10 : TALER_MERCHANT_parse_error_details_ (const json_t *response,
34 : unsigned int http_status,
35 : struct TALER_MERCHANT_HttpResponse *hr)
36 : {
37 : const json_t *jc;
38 :
39 10 : memset (hr, 0, sizeof (*hr));
40 10 : hr->reply = response;
41 10 : hr->http_status = http_status;
42 10 : if (NULL == response)
43 : {
44 0 : hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE;
45 0 : return;
46 : }
47 10 : hr->ec = TALER_JSON_get_error_code (response);
48 10 : hr->hint = TALER_JSON_get_error_hint (response);
49 :
50 : /* handle 'exchange_http_status' */
51 10 : jc = json_object_get (response,
52 : "exchange_http_status");
53 : /* The caller already knows that the JSON represents an error,
54 : so we are dealing with a missing error code here. */
55 10 : if (NULL == jc)
56 4 : return; /* no need to bother with exchange_code/hint if we had no status */
57 6 : if (! json_is_integer (jc))
58 : {
59 0 : GNUNET_break_op (0);
60 0 : return;
61 : }
62 6 : hr->exchange_http_status = (unsigned int) json_integer_value (jc);
63 :
64 : /* handle 'exchange_reply' */
65 6 : jc = json_object_get (response,
66 : "exchange_reply");
67 6 : if (! json_is_object (jc))
68 : {
69 0 : GNUNET_break_op (0);
70 : }
71 : else
72 : {
73 6 : hr->exchange_reply = jc;
74 : }
75 :
76 : /* handle 'exchange_code' */
77 6 : jc = json_object_get (response,
78 : "exchange_code");
79 : /* The caller already knows that the JSON represents an error,
80 : so we are dealing with a missing error code here. */
81 6 : if (NULL == jc)
82 0 : return; /* no need to bother with exchange-hint if we had no code */
83 6 : if (! json_is_integer (jc))
84 : {
85 0 : GNUNET_break_op (0);
86 0 : hr->exchange_code = TALER_EC_INVALID;
87 : }
88 : else
89 : {
90 6 : hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc);
91 : }
92 :
93 : /* handle 'exchange-hint' */
94 6 : jc = json_object_get (response,
95 : "exchange-hint");
96 : /* The caller already knows that the JSON represents an error,
97 : so we are dealing with a missing error code here. */
98 6 : if (NULL == jc)
99 6 : return;
100 0 : if (! json_is_string (jc))
101 : {
102 0 : GNUNET_break_op (0);
103 : }
104 : else
105 : {
106 0 : hr->exchange_hint = json_string_value (jc);
107 : }
108 : }
109 :
110 :
111 : enum GNUNET_GenericReturnValue
112 19 : TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
113 : struct TALER_MERCHANT_PayUriData *parse_data)
114 : {
115 19 : char *cp = GNUNET_strdup (pay_uri);
116 : struct GNUNET_Uri u;
117 :
118 19 : if (0 !=
119 19 : GNUNET_uri_parse (&u,
120 : cp))
121 : {
122 0 : GNUNET_free (cp);
123 0 : GNUNET_break_op (0);
124 0 : return GNUNET_SYSERR;
125 : }
126 19 : if ((0 != strcasecmp ("taler",
127 19 : u.scheme)) &&
128 15 : (0 != strcasecmp ("taler+http",
129 15 : u.scheme)))
130 : {
131 1 : fprintf (stderr,
132 : "Bad schema %s\n",
133 : u.scheme);
134 1 : GNUNET_free (cp);
135 1 : GNUNET_break_op (0);
136 1 : return GNUNET_SYSERR;
137 : }
138 18 : parse_data->use_http = (0 == strcasecmp ("taler+http",
139 18 : u.scheme));
140 18 : if (0 != strcasecmp ("pay",
141 18 : u.host))
142 : {
143 1 : GNUNET_break_op (0);
144 1 : GNUNET_free (cp);
145 1 : return GNUNET_SYSERR;
146 : }
147 :
148 : {
149 : char *order_id;
150 : char *mpp;
151 17 : char *session_id = strrchr (u.path,
152 : '/');
153 17 : struct TALER_ClaimTokenP *claim_token = NULL;
154 :
155 17 : if (NULL == session_id)
156 : {
157 0 : GNUNET_break_op (0);
158 0 : GNUNET_free (cp);
159 0 : return GNUNET_SYSERR;
160 : }
161 17 : *session_id = '\0';
162 17 : ++session_id;
163 :
164 17 : order_id = strrchr (u.path,
165 : '/');
166 17 : if (NULL == order_id)
167 : {
168 1 : GNUNET_break_op (0);
169 1 : GNUNET_free (cp);
170 1 : return GNUNET_SYSERR;
171 : }
172 16 : *order_id = '\0';
173 16 : ++order_id;
174 16 : mpp = strchr (u.path,
175 : '/');
176 16 : if (NULL != mpp)
177 : {
178 1 : *mpp = '\0';
179 1 : ++mpp;
180 : }
181 :
182 : {
183 16 : char *ct_str = u.query;
184 : char *ct_data;
185 :
186 16 : if (NULL != ct_str)
187 : {
188 7 : ct_data = strchr (u.query,
189 : '=');
190 7 : if (NULL == ct_data)
191 : {
192 0 : GNUNET_break_op (0);
193 0 : GNUNET_free (cp);
194 0 : return GNUNET_SYSERR;
195 : }
196 7 : *ct_data = '\0';
197 7 : ++ct_data;
198 7 : claim_token = GNUNET_new (struct TALER_ClaimTokenP);
199 7 : if ( (0 != strcmp ("c",
200 14 : u.query)) ||
201 : (GNUNET_OK !=
202 7 : GNUNET_STRINGS_string_to_data (ct_data,
203 : strlen (ct_data),
204 : claim_token,
205 : sizeof (*claim_token))) )
206 : {
207 0 : GNUNET_break_op (0);
208 0 : GNUNET_free (claim_token);
209 0 : GNUNET_free (cp);
210 0 : return GNUNET_SYSERR;
211 : }
212 : }
213 : }
214 :
215 : parse_data->merchant_prefix_path
216 16 : = (NULL == mpp)
217 : ? NULL
218 16 : : GNUNET_strdup (mpp);
219 16 : parse_data->merchant_host = GNUNET_strdup (u.path);
220 16 : parse_data->order_id = GNUNET_strdup (order_id);
221 : parse_data->session_id
222 32 : = (0 < strlen (session_id))
223 10 : ? GNUNET_strdup (session_id)
224 16 : : NULL;
225 16 : parse_data->claim_token = claim_token;
226 : parse_data->ssid
227 32 : = (NULL == u.fragment)
228 : ? NULL
229 16 : : GNUNET_strdup (u.fragment);
230 : }
231 16 : GNUNET_free (cp);
232 16 : return GNUNET_OK;
233 : }
234 :
235 :
236 : void
237 16 : TALER_MERCHANT_parse_pay_uri_free (
238 : struct TALER_MERCHANT_PayUriData *parse_data)
239 : {
240 16 : GNUNET_free (parse_data->merchant_host);
241 16 : GNUNET_free (parse_data->merchant_prefix_path);
242 16 : GNUNET_free (parse_data->order_id);
243 16 : GNUNET_free (parse_data->session_id);
244 16 : GNUNET_free (parse_data->claim_token);
245 16 : GNUNET_free (parse_data->ssid);
246 16 : }
247 :
248 :
249 : enum GNUNET_GenericReturnValue
250 11 : TALER_MERCHANT_parse_refund_uri (
251 : const char *refund_uri,
252 : struct TALER_MERCHANT_RefundUriData *parse_data)
253 : {
254 11 : char *cp = GNUNET_strdup (refund_uri);
255 : struct GNUNET_Uri u;
256 :
257 11 : if (0 !=
258 11 : GNUNET_uri_parse (&u,
259 : cp))
260 : {
261 0 : GNUNET_free (cp);
262 0 : GNUNET_break_op (0);
263 0 : return GNUNET_SYSERR;
264 : }
265 11 : if ((0 != strcasecmp ("taler",
266 11 : u.scheme)) &&
267 8 : (0 != strcasecmp ("taler+http",
268 8 : u.scheme)))
269 : {
270 1 : GNUNET_free (cp);
271 1 : GNUNET_break_op (0);
272 1 : return GNUNET_SYSERR;
273 : }
274 10 : parse_data->use_http = (0 == strcasecmp ("taler+http",
275 10 : u.scheme));
276 :
277 10 : if (0 != strcasecmp ("refund",
278 10 : u.host))
279 : {
280 1 : GNUNET_break_op (0);
281 1 : GNUNET_free (cp);
282 1 : return GNUNET_SYSERR;
283 : }
284 :
285 : {
286 : char *order_id;
287 9 : char *last_seg = strrchr (u.path,
288 : '/');
289 :
290 9 : if (NULL == last_seg)
291 : {
292 0 : GNUNET_break_op (0);
293 0 : GNUNET_free (cp);
294 0 : return GNUNET_SYSERR;
295 : }
296 9 : *last_seg = '\0';
297 9 : ++last_seg;
298 :
299 9 : order_id = strrchr (u.path,
300 : '/');
301 9 : if (NULL == order_id)
302 : {
303 1 : GNUNET_break_op (0);
304 1 : GNUNET_free (cp);
305 1 : return GNUNET_SYSERR;
306 : }
307 8 : *order_id = '\0';
308 8 : ++order_id;
309 8 : if (0 != strlen (last_seg))
310 : {
311 0 : GNUNET_break_op (0);
312 0 : GNUNET_free (cp);
313 0 : return GNUNET_SYSERR;
314 : }
315 :
316 : {
317 : char *mpp;
318 :
319 8 : mpp = strchr (u.path,
320 : '/');
321 8 : if (NULL != mpp)
322 : {
323 1 : *mpp = '\0';
324 1 : ++mpp;
325 : }
326 :
327 : parse_data->merchant_prefix_path
328 8 : = (NULL == mpp)
329 : ? NULL
330 8 : : GNUNET_strdup (mpp);
331 : }
332 8 : parse_data->merchant_host = GNUNET_strdup (u.path);
333 8 : parse_data->order_id = GNUNET_strdup (order_id);
334 : parse_data->ssid
335 16 : = (NULL == u.fragment)
336 : ? NULL
337 8 : : GNUNET_strdup (u.fragment);
338 : }
339 8 : GNUNET_free (cp);
340 8 : return GNUNET_OK;
341 : }
342 :
343 :
344 : void
345 8 : TALER_MERCHANT_parse_refund_uri_free (
346 : struct TALER_MERCHANT_RefundUriData *parse_data)
347 : {
348 8 : GNUNET_free (parse_data->merchant_host);
349 8 : GNUNET_free (parse_data->merchant_prefix_path);
350 8 : GNUNET_free (parse_data->order_id);
351 8 : GNUNET_free (parse_data->ssid);
352 8 : }
353 :
354 :
355 : void
356 64 : TALER_MERCHANT_handle_order_creation_response_ (
357 : TALER_MERCHANT_PostOrdersCallback cb,
358 : void *cb_cls,
359 : long response_code,
360 : const json_t *json)
361 : {
362 64 : struct TALER_MERCHANT_PostOrdersReply por = {
363 64 : .hr.http_status = (unsigned int) response_code,
364 : .hr.reply = json
365 : };
366 : struct TALER_ClaimTokenP token;
367 :
368 64 : switch (response_code)
369 : {
370 0 : case 0:
371 0 : por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
372 0 : break;
373 44 : case MHD_HTTP_OK:
374 : {
375 : bool no_token;
376 : struct GNUNET_JSON_Specification spec[] = {
377 44 : GNUNET_JSON_spec_string ("order_id",
378 : &por.details.ok.order_id),
379 44 : GNUNET_JSON_spec_mark_optional (
380 : GNUNET_JSON_spec_fixed_auto ("token",
381 : &token),
382 : &no_token),
383 44 : GNUNET_JSON_spec_end ()
384 : };
385 :
386 44 : if (GNUNET_OK !=
387 44 : GNUNET_JSON_parse (json,
388 : spec,
389 : NULL, NULL))
390 : {
391 0 : GNUNET_break_op (0);
392 0 : por.hr.http_status = 0;
393 0 : por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
394 : }
395 : else
396 : {
397 44 : if (! no_token)
398 40 : por.details.ok.token = &token;
399 : }
400 : }
401 44 : break;
402 0 : case MHD_HTTP_BAD_REQUEST:
403 0 : json_dumpf (json,
404 : stderr,
405 : JSON_INDENT (2));
406 0 : por.hr.ec = TALER_JSON_get_error_code (json);
407 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
408 : /* This should never happen, either us or
409 : the merchant is buggy (or API version conflict);
410 : just pass JSON reply to the application */
411 0 : break;
412 0 : case MHD_HTTP_UNAUTHORIZED:
413 0 : por.hr.ec = TALER_JSON_get_error_code (json);
414 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
415 : /* Nothing really to verify, merchant says we need to authenticate. */
416 0 : break;
417 0 : case MHD_HTTP_FORBIDDEN:
418 : /* Nothing really to verify, merchant says one
419 : of the signatures is invalid; as we checked them,
420 : this should never happen, we should pass the JSON
421 : reply to the application */
422 0 : por.hr.ec = TALER_JSON_get_error_code (json);
423 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
424 0 : break;
425 6 : case MHD_HTTP_NOT_FOUND:
426 : /* Nothing really to verify, this should never
427 : happen, we should pass the JSON reply to the application */
428 6 : por.hr.ec = TALER_JSON_get_error_code (json);
429 6 : por.hr.hint = TALER_JSON_get_error_hint (json);
430 6 : break;
431 12 : case MHD_HTTP_CONFLICT:
432 12 : por.hr.ec = TALER_JSON_get_error_code (json);
433 12 : por.hr.hint = TALER_JSON_get_error_hint (json);
434 12 : break;
435 2 : case MHD_HTTP_GONE:
436 : /* The quantity of some product requested was not available. */
437 : {
438 :
439 : struct GNUNET_JSON_Specification spec[] = {
440 2 : GNUNET_JSON_spec_string (
441 : "product_id",
442 : &por.details.gone.product_id),
443 2 : GNUNET_JSON_spec_uint64 (
444 : "requested_quantity",
445 : &por.details.gone.requested_quantity),
446 2 : GNUNET_JSON_spec_uint64 (
447 : "available_quantity",
448 : &por.details.gone.available_quantity),
449 2 : GNUNET_JSON_spec_mark_optional (
450 : GNUNET_JSON_spec_timestamp (
451 : "restock_expected",
452 : &por.details.gone.restock_expected),
453 : NULL),
454 2 : GNUNET_JSON_spec_end ()
455 : };
456 :
457 2 : if (GNUNET_OK !=
458 2 : GNUNET_JSON_parse (json,
459 : spec,
460 : NULL, NULL))
461 : {
462 0 : GNUNET_break_op (0);
463 0 : por.hr.http_status = 0;
464 0 : por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
465 : }
466 2 : break;
467 : }
468 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
469 : /* Order total too high for legal reasons */
470 0 : por.hr.ec = TALER_JSON_get_error_code (json);
471 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
472 0 : break;
473 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
474 : /* Server had an internal issue; we should retry,
475 : but this API leaves this to the application */
476 0 : por.hr.ec = TALER_JSON_get_error_code (json);
477 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
478 0 : break;
479 0 : default:
480 : /* unexpected response code */
481 0 : por.hr.ec = TALER_JSON_get_error_code (json);
482 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
483 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
484 : "Unexpected response code %u/%d\n",
485 : (unsigned int) response_code,
486 : (int) por.hr.ec);
487 0 : GNUNET_break_op (0);
488 0 : break;
489 : } // end of switch
490 64 : cb (cb_cls,
491 : &por);
492 64 : }
|