/* This file is part of TALER Copyright (C) 2020-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TALER; see the file COPYING.LGPL. If not, see */ /** * @file merchant_api_common.c * @brief Implementation of common logic for libtalermerchant * @author Christian Grothoff * @author Priscilla Huang */ #include "platform.h" #include #include "taler_merchant_service.h" #include "merchant_api_common.h" #include #include void TALER_MERCHANT_parse_error_details_ (const json_t *response, unsigned int http_status, struct TALER_MERCHANT_HttpResponse *hr) { const json_t *jc; memset (hr, 0, sizeof (*hr)); hr->reply = response; hr->http_status = http_status; if (NULL == response) { hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE; return; } hr->ec = TALER_JSON_get_error_code (response); hr->hint = TALER_JSON_get_error_hint (response); /* handle 'exchange_http_status' */ jc = json_object_get (response, "exchange_http_status"); /* The caller already knows that the JSON represents an error, so we are dealing with a missing error code here. */ if (NULL == jc) return; /* no need to bother with exchange_code/hint if we had no status */ if (! json_is_integer (jc)) { GNUNET_break_op (0); return; } hr->exchange_http_status = (unsigned int) json_integer_value (jc); /* handle 'exchange_reply' */ jc = json_object_get (response, "exchange_reply"); if (! json_is_object (jc)) { GNUNET_break_op (0); } else { hr->exchange_reply = jc; } /* handle 'exchange_code' */ jc = json_object_get (response, "exchange_code"); /* The caller already knows that the JSON represents an error, so we are dealing with a missing error code here. */ if (NULL == jc) return; /* no need to bother with exchange-hint if we had no code */ if (! json_is_integer (jc)) { GNUNET_break_op (0); hr->exchange_code = TALER_EC_INVALID; } else { hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc); } /* handle 'exchange-hint' */ jc = json_object_get (response, "exchange-hint"); /* The caller already knows that the JSON represents an error, so we are dealing with a missing error code here. */ if (NULL == jc) return; if (! json_is_string (jc)) { GNUNET_break_op (0); } else { hr->exchange_hint = json_string_value (jc); } } enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_pay_uri (const char *pay_uri, struct TALER_MERCHANT_PayUriData *parse_data) { char *cp = GNUNET_strdup (pay_uri); struct GNUNET_Uri u; if (0 != GNUNET_uri_parse (&u, cp)) { GNUNET_free (cp); GNUNET_break_op (0); return GNUNET_SYSERR; } if ((0 != strcasecmp ("taler", u.scheme)) && (0 != strcasecmp ("taler+http", u.scheme))) { fprintf (stderr, "Bad schema %s\n", u.scheme); GNUNET_free (cp); GNUNET_break_op (0); return GNUNET_SYSERR; } parse_data->use_http = (0 == strcasecmp ("taler+http", u.scheme)); if (0 != strcasecmp ("pay", u.host)) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } { char *order_id; char *mpp; char *session_id = strrchr (u.path, '/'); struct TALER_ClaimTokenP *claim_token = NULL; if (NULL == session_id) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } *session_id = '\0'; ++session_id; order_id = strrchr (u.path, '/'); if (NULL == order_id) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } *order_id = '\0'; ++order_id; mpp = strchr (u.path, '/'); if (NULL != mpp) { *mpp = '\0'; ++mpp; } { char *ct_str = u.query; char *ct_data; if (NULL != ct_str) { ct_data = strchr (u.query, '='); if (NULL == ct_data) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } *ct_data = '\0'; ++ct_data; claim_token = GNUNET_new (struct TALER_ClaimTokenP); if ( (0 != strcmp ("c", u.query)) || (GNUNET_OK != GNUNET_STRINGS_string_to_data (ct_data, strlen (ct_data), claim_token, sizeof (*claim_token))) ) { GNUNET_break_op (0); GNUNET_free (claim_token); GNUNET_free (cp); return GNUNET_SYSERR; } } } parse_data->merchant_prefix_path = (NULL == mpp) ? NULL : GNUNET_strdup (mpp); parse_data->merchant_host = GNUNET_strdup (u.path); parse_data->order_id = GNUNET_strdup (order_id); parse_data->session_id = (0 < strlen (session_id)) ? GNUNET_strdup (session_id) : NULL; parse_data->claim_token = claim_token; parse_data->ssid = (NULL == u.fragment) ? NULL : GNUNET_strdup (u.fragment); } GNUNET_free (cp); return GNUNET_OK; } void TALER_MERCHANT_parse_pay_uri_free ( struct TALER_MERCHANT_PayUriData *parse_data) { GNUNET_free (parse_data->merchant_host); GNUNET_free (parse_data->merchant_prefix_path); GNUNET_free (parse_data->order_id); GNUNET_free (parse_data->session_id); GNUNET_free (parse_data->claim_token); GNUNET_free (parse_data->ssid); } enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_refund_uri ( const char *refund_uri, struct TALER_MERCHANT_RefundUriData *parse_data) { char *cp = GNUNET_strdup (refund_uri); struct GNUNET_Uri u; if (0 != GNUNET_uri_parse (&u, cp)) { GNUNET_free (cp); GNUNET_break_op (0); return GNUNET_SYSERR; } if ((0 != strcasecmp ("taler", u.scheme)) && (0 != strcasecmp ("taler+http", u.scheme))) { GNUNET_free (cp); GNUNET_break_op (0); return GNUNET_SYSERR; } parse_data->use_http = (0 == strcasecmp ("taler+http", u.scheme)); if (0 != strcasecmp ("refund", u.host)) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } { char *order_id; char *last_seg = strrchr (u.path, '/'); if (NULL == last_seg) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } *last_seg = '\0'; ++last_seg; order_id = strrchr (u.path, '/'); if (NULL == order_id) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } *order_id = '\0'; ++order_id; if (0 != strlen (last_seg)) { GNUNET_break_op (0); GNUNET_free (cp); return GNUNET_SYSERR; } { char *mpp; mpp = strchr (u.path, '/'); if (NULL != mpp) { *mpp = '\0'; ++mpp; } parse_data->merchant_prefix_path = (NULL == mpp) ? NULL : GNUNET_strdup (mpp); } parse_data->merchant_host = GNUNET_strdup (u.path); parse_data->order_id = GNUNET_strdup (order_id); parse_data->ssid = (NULL == u.fragment) ? NULL : GNUNET_strdup (u.fragment); } GNUNET_free (cp); return GNUNET_OK; } void TALER_MERCHANT_parse_refund_uri_free ( struct TALER_MERCHANT_RefundUriData *parse_data) { GNUNET_free (parse_data->merchant_host); GNUNET_free (parse_data->merchant_prefix_path); GNUNET_free (parse_data->order_id); GNUNET_free (parse_data->ssid); } void TALER_MERCHANT_handle_order_creation_response_ ( TALER_MERCHANT_PostOrdersCallback cb, void *cb_cls, long response_code, const json_t *json) { struct TALER_MERCHANT_PostOrdersReply por = { .hr.http_status = (unsigned int) response_code, .hr.reply = json }; struct TALER_ClaimTokenP token; switch (response_code) { case 0: por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { bool no_token; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("order_id", &por.details.ok.order_id), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("token", &token), &no_token), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (json, spec, NULL, NULL)) { GNUNET_break_op (0); por.hr.http_status = 0; por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; } else { if (! no_token) por.details.ok.token = &token; } } break; case MHD_HTTP_BAD_REQUEST: json_dumpf (json, stderr, JSON_INDENT (2)); por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); /* This should never happen, either us or the merchant is buggy (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_UNAUTHORIZED: por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, merchant says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_CONFLICT: por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_GONE: /* The quantity of some product requested was not available. */ { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ( "product_id", &por.details.gone.product_id), GNUNET_JSON_spec_uint64 ( "requested_quantity", &por.details.gone.requested_quantity), GNUNET_JSON_spec_uint64 ( "available_quantity", &por.details.gone.available_quantity), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ( "restock_expected", &por.details.gone.restock_expected), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (json, spec, NULL, NULL)) { GNUNET_break_op (0); por.hr.http_status = 0; por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; } break; } case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ por.hr.ec = TALER_JSON_get_error_code (json); por.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, (int) por.hr.ec); GNUNET_break_op (0); break; } // end of switch cb (cb_cls, &por); }