/* This file is part of TALER Copyright (C) 2020 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 lib/merchant_api_common.c * @brief Implementation of common logic for libtalermerchant * @author Christian Grothoff */ #include "platform.h" #include #include #include /* just for HTTP status codes */ #include #include #include "taler_merchant_service.h" #include /** * Take a @a response from the merchant API that (presumably) contains * error details and setup the corresponding @a hr structure. Internally * used to convert merchant's responses in to @a hr. * * @param response, if NULL we will report #TALER_EC_INVALID_RESPONSE in `ec * @param http_status http status to use * @param[out] hr response object to initialize, fields will * only be valid as long as @a response is valid as well */ 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_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) 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); } } /** * Construct a new base URL using the existing @a base_url * and the given @a instance_id. The result WILL end with * '/'. * * @param base_url a merchant base URL without "/instances/" in it, * must not be the empty string; MAY end with '/'. * @param instance_id ID of an instance * @return "${base_url}/instances/${instance_id}/" */ char * TALER_MERCHANT_baseurl_add_instance (const char *base_url, const char *instance_id) { char *ret; bool end_sl; if ('\0' == *base_url) { GNUNET_break (0); return NULL; } end_sl = '/' == base_url[strlen (base_url) - 1]; GNUNET_asprintf (&ret, (end_sl) ? "%sinstances/%s/" : "%s/instances/%s/", base_url, instance_id); return ret; } /** * Parses the URI scheme and action of a URI. Ensures that the scheme is either * 'taler' or 'taler+http'. * * @param uri the uri to parse. * @param[out] action the action the URI is indicating. * @param[out] rest the substring of the URI following the action. * @return #GNUNET_SYSERR if the URI is malformed, #GNUNET_OK otherwise. */ static int parse_taler_uri_scheme_action (const char *uri, char **action, char **rest) { char *scheme = GNUNET_strdup (uri); /* Check that the uri starts with "taler://pay" or "taler+http://pay" and then remove it */ char *path = strchr (scheme, ':'); if ( (NULL == path) || (strlen (path) < 3) ) { GNUNET_free (scheme); GNUNET_break_op (0); return GNUNET_SYSERR; } path += 3; /* make path point to 'pay' */ { char path_begin = *path; *path = '\0'; if ((0 != strcmp ("taler://", scheme)) && (0 != strcmp ("taler+http://", scheme))) { GNUNET_free (scheme); GNUNET_break_op (0); return GNUNET_SYSERR; } *path = path_begin; } { char *pqf = strchr (path, '/'); if (NULL == pqf) { GNUNET_free (scheme); GNUNET_break_op (0); return GNUNET_SYSERR; } *pqf = '\0'; ++pqf; *rest = GNUNET_strdup (pqf); } *action = GNUNET_strdup (path); GNUNET_free (scheme); return GNUNET_OK; } /** * Extracts information from a taler://pay URI. * * @param pay_uri the URI to parse. * @param[out] parse_data data extracted from the URI. Must be free'd. * @return #GNUNET_SYSERR if @e pay_uri is malformed, #GNUNET_OK otherwise. */ int TALER_MERCHANT_parse_pay_uri (const char *pay_uri, struct TALER_MERCHANT_PayUriData *parse_data) { char *path = NULL; { char *action = NULL; if ((GNUNET_OK != parse_taler_uri_scheme_action (pay_uri, &action, &path)) || (0 != strcmp ("pay", action))) { GNUNET_break_op (0); GNUNET_free (action); GNUNET_free (path); return GNUNET_SYSERR; } GNUNET_free (action); } { char *mpp; char *order_id; char *session_id = strrchr (path, '/'); struct TALER_ClaimTokenP *claim_token = NULL; char *ssid; if (NULL == session_id) { GNUNET_break_op (0); GNUNET_free (path); return GNUNET_SYSERR; } *session_id = '\0'; ++session_id; order_id = strrchr (path, '/'); if (NULL == order_id) { GNUNET_break_op (0); GNUNET_free (path); return GNUNET_SYSERR; } *order_id = '\0'; ++order_id; { char *ct_str = strchr (session_id, '?'); char *ct_data; if (NULL != ct_str) { *ct_str = '\0'; ++ct_str; } ssid = strchr (session_id, '#'); if (NULL != ssid) { *ssid = '\0'; ++ssid; } if (NULL != ct_str) { ct_data = strchr (ct_str, '='); if (NULL == ct_data) { GNUNET_break_op (0); GNUNET_free (path); return GNUNET_SYSERR; } *ct_data = '\0'; ++ct_data; claim_token = GNUNET_new (struct TALER_ClaimTokenP); if ((0 != strcmp ("c", ct_str)) || (GNUNET_OK != GNUNET_STRINGS_string_to_data (ct_data, strlen (ct_data), claim_token, sizeof (*claim_token)))) { GNUNET_break_op (0); GNUNET_free (path); GNUNET_free (claim_token); return GNUNET_SYSERR; } } } mpp = strchr (path, '/'); if (NULL != mpp) { *mpp = '\0'; ++mpp; } parse_data->merchant_host = GNUNET_strdup (path); parse_data->merchant_prefix_path = (NULL == mpp) ? NULL : GNUNET_strdup (mpp); 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 == ssid) ? NULL : GNUNET_strdup (ssid); } GNUNET_free (path); return GNUNET_OK; } /** * Frees data contained in the result of parsing a taler://pay URI. * * @param parse_data the data to free. */ 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); } /** * Extracts information from a taler://refund URI. * * @param refund_uri the URI to parse. * @param[out] parse_data data extracted from the URI. Must be free'd. * @return #GNUNET_SYSERR if @e refund_uri is malformed, #GNUNET_OK otherwise. */ int TALER_MERCHANT_parse_refund_uri ( const char *refund_uri, struct TALER_MERCHANT_RefundUriData *parse_data) { char *path = NULL; { char *action = NULL; if ((GNUNET_OK != parse_taler_uri_scheme_action (refund_uri, &action, &path)) || (0 != strcmp ("refund", action))) { GNUNET_break_op (0); GNUNET_free (action); GNUNET_free (path); return GNUNET_SYSERR; } GNUNET_free (action); } { char *mpp; char *order_id; char *last_seg = strrchr (path, '/'); char *ssid; if (NULL == last_seg) { GNUNET_break_op (0); GNUNET_free (path); return GNUNET_SYSERR; } *last_seg = '\0'; ++last_seg; order_id = strrchr (path, '/'); if (NULL == order_id) { GNUNET_break_op (0); GNUNET_free (path); return GNUNET_SYSERR; } *order_id = '\0'; ++order_id; ssid = strchr (last_seg, '#'); if (NULL != ssid) { *ssid = '\0'; ++ssid; } if (0 != strlen (last_seg)) { GNUNET_break_op (0); GNUNET_free (path); return GNUNET_SYSERR; } mpp = strchr (path, '/'); if (NULL != mpp) { *mpp = '\0'; ++mpp; } parse_data->merchant_host = GNUNET_strdup (path); parse_data->merchant_prefix_path = (NULL == mpp) ? NULL : GNUNET_strdup (mpp); parse_data->order_id = GNUNET_strdup (order_id); parse_data->ssid = (NULL == ssid) ? NULL : GNUNET_strdup (ssid); } GNUNET_free (path); return GNUNET_OK; } /** * Frees data contained in the result of parsing a taler://refund URI. * * @param parse_data the data to free. */ 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); }