diff options
Diffstat (limited to 'src/restclient/anastasis_api_truth_solve.c')
-rw-r--r-- | src/restclient/anastasis_api_truth_solve.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/src/restclient/anastasis_api_truth_solve.c b/src/restclient/anastasis_api_truth_solve.c new file mode 100644 index 0000000..9002a63 --- /dev/null +++ b/src/restclient/anastasis_api_truth_solve.c @@ -0,0 +1,437 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021, 2022 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file restclient/anastasis_api_truth_solve.c + * @brief Implementation of the POST /truth/$TID/solve request + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * @brief A Contract Operation Handle + */ +struct ANASTASIS_TruthSolveOperation +{ + /** + * The url for this request, including parameters. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_TruthSolveCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Payment URI we received from the service, or NULL. + */ + char *pay_uri; + + /** + * Content type of the body. + */ + char *content_type; +}; + + +void +ANASTASIS_truth_solve_cancel ( + struct ANASTASIS_TruthSolveOperation *tso) +{ + if (NULL != tso->job) + { + GNUNET_CURL_job_cancel (tso->job); + tso->job = NULL; + } + GNUNET_free (tso->pay_uri); + GNUNET_free (tso->url); + GNUNET_free (tso->content_type); + TALER_curl_easy_post_finished (&tso->ctx); + GNUNET_free (tso); +} + + +/** + * Process POST /truth/$TID/solve response + * + * @param cls our `struct ANASTASIS_TruthSolveOperation *` + * @param response_code the HTTP status + * @param data the body of the response + * @param data_size number of bytes in @a data + */ +static void +handle_truth_solve_finished (void *cls, + long response_code, + const void *data, + size_t data_size) +{ + struct ANASTASIS_TruthSolveOperation *tso = cls; + struct ANASTASIS_TruthSolveReply tsr = { + .http_status = response_code + }; + + tso->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from POST /truth/$TID/solve\n"); + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (sizeof (tsr.details.success.eks) != data_size) + { + GNUNET_break_op (0); + tsr.http_status = 0; + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + /* Success, call callback with all details! */ + memcpy (&tsr.details.success.eks, + data, + data_size); + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == tso->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (tso->pay_uri, + &pd)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse `%s'\n", + tso->pay_uri); + tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &tsr.details.payment_required.ps, + sizeof (tsr.details.payment_required.ps))) + { + GNUNET_break (0); + tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + tsr.details.payment_required.pd = &pd; + tsr.details.payment_required.payment_request = tso->pay_uri; + tso->cb (tso->cb_cls, + &tsr); + TALER_MERCHANT_parse_pay_uri_free (&pd); + ANASTASIS_truth_solve_cancel (tso); + return; + } + break; + case MHD_HTTP_FORBIDDEN: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_NOT_FOUND: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_REQUEST_TIMEOUT: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_TOO_MANY_REQUESTS: + { + json_t *reply; + + reply = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == reply) + { + GNUNET_break_op (0); + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ( + "request_limit", + &tsr.details.too_many_requests.request_limit), + GNUNET_JSON_spec_relative_time ( + "request_frequency", + &tsr.details.too_many_requests.request_frequency), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + json_decref (reply); + break; + } + json_decref (reply); + break; + } + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_BAD_GATEWAY: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + default: + /* unexpected response code */ + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to POST /truth/$TID/solve\n", + (unsigned int) response_code, + (int) tsr.ec); + break; + } + tso->cb (tso->cb_cls, + &tsr); + ANASTASIS_truth_solve_cancel (tso); +} + + +/** + * Patch value in @a val, replacing new line with '\0'. + * + * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. + */ +static void +patch_value (char *val) +{ + size_t len; + + /* found location URI we care about! */ + len = strlen (val); + while ( (len > 0) && + ( ('\n' == val[len - 1]) || + ('\r' == val[len - 1]) ) ) + { + len--; + val[len] = '\0'; + } +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_StorePolicyOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_TruthSolveOperation *tso = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_TALER)) + { + /* found payment URI we care about! */ + GNUNET_free (tso->pay_uri); + tso->pay_uri = GNUNET_strdup (hdr_val); + patch_value (tso->pay_uri); + } + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONTENT_TYPE)) + { + /* found location URI we care about! */ + GNUNET_free (tso->content_type); + tso->content_type = GNUNET_strdup (hdr_val); + patch_value (tso->content_type); + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_TruthSolveOperation * +ANASTASIS_truth_solve ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, + const struct ANASTASIS_PaymentSecretP *payment_secret, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_HashCode *hashed_answer, + ANASTASIS_TruthSolveCallback cb, + void *cb_cls) +{ + struct ANASTASIS_TruthSolveOperation *tso; + CURL *eh; + char *path; + unsigned long long tms; + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("truth_decryption_key", + truth_key), + GNUNET_JSON_pack_data_auto ("h_response", + hashed_answer), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("payment_secret", + payment_secret))); + GNUNET_assert (NULL != body); + + tms = (unsigned long long) (timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + tso = GNUNET_new (struct ANASTASIS_TruthSolveOperation); + tso->cb = cb; + tso->cb_cls = cb_cls; + { + char *uuid_str; + + uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, + sizeof (*truth_uuid)); + GNUNET_asprintf (&path, + "truth/%s/solve", + uuid_str); + GNUNET_free (uuid_str); + } + { + char timeout_ms[32]; + + GNUNET_snprintf (timeout_ms, + sizeof (timeout_ms), + "%llu", + tms); + tso->url = TALER_url_join (backend_url, + path, + "timeout_ms", + (! GNUNET_TIME_relative_is_zero (timeout)) + ? timeout_ms + : NULL, + NULL); + } + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (tso->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&tso->ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (tso->url); + GNUNET_free (tso); + return NULL; + } + json_decref (body); + if (0 != tms) + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 5000))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + tso)); + tso->job = GNUNET_CURL_job_add_raw (ctx, + eh, + tso->ctx.headers, + &handle_truth_solve_finished, + tso); + return tso; +} + + +/* end of anastasis_api_truth_solve.c */ |