anastasis_api_truth_solve.c (12355B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020, 2021, 2022 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 Anastasis 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file restclient/anastasis_api_truth_solve.c 18 * @brief Implementation of the POST /truth/$TID/solve request 19 * @author Christian Grothoff 20 * @author Dennis Neufeld 21 * @author Dominik Meister 22 */ 23 #include "platform.h" 24 #include <curl/curl.h> 25 #include <jansson.h> 26 #include <microhttpd.h> /* just for HTTP status codes */ 27 #include "anastasis_service.h" 28 #include "anastasis_api_curl_defaults.h" 29 #include <taler/taler_json_lib.h> 30 #include <taler/taler_curl_lib.h> 31 #include <taler/taler_merchant_service.h> 32 33 34 /** 35 * @brief A Contract Operation Handle 36 */ 37 struct ANASTASIS_TruthSolveOperation 38 { 39 /** 40 * The url for this request, including parameters. 41 */ 42 char *url; 43 44 /** 45 * Handle for the request. 46 */ 47 struct GNUNET_CURL_Job *job; 48 49 /** 50 * Function to call with the result. 51 */ 52 ANASTASIS_TruthSolveCallback cb; 53 54 /** 55 * Closure for @a cb. 56 */ 57 void *cb_cls; 58 59 /** 60 * Context for #TEH_curl_easy_post(). Keeps the data that must 61 * persist for Curl to make the upload. 62 */ 63 struct TALER_CURL_PostContext ctx; 64 65 /** 66 * Payment URI we received from the service, or NULL. 67 */ 68 char *pay_uri; 69 70 /** 71 * Content type of the body. 72 */ 73 char *content_type; 74 }; 75 76 77 void 78 ANASTASIS_truth_solve_cancel ( 79 struct ANASTASIS_TruthSolveOperation *tso) 80 { 81 if (NULL != tso->job) 82 { 83 GNUNET_CURL_job_cancel (tso->job); 84 tso->job = NULL; 85 } 86 GNUNET_free (tso->pay_uri); 87 GNUNET_free (tso->url); 88 GNUNET_free (tso->content_type); 89 TALER_curl_easy_post_finished (&tso->ctx); 90 GNUNET_free (tso); 91 } 92 93 94 /** 95 * Process POST /truth/$TID/solve response 96 * 97 * @param cls our `struct ANASTASIS_TruthSolveOperation *` 98 * @param response_code the HTTP status 99 * @param data the body of the response 100 * @param data_size number of bytes in @a data 101 */ 102 static void 103 handle_truth_solve_finished (void *cls, 104 long response_code, 105 const void *data, 106 size_t data_size) 107 { 108 struct ANASTASIS_TruthSolveOperation *tso = cls; 109 struct ANASTASIS_TruthSolveReply tsr = { 110 .http_status = response_code 111 }; 112 113 tso->job = NULL; 114 switch (response_code) 115 { 116 case 0: 117 /* Hard error */ 118 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 119 "Backend didn't even return from POST /truth/$TID/solve\n"); 120 tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 121 break; 122 case MHD_HTTP_OK: 123 if (sizeof (tsr.details.success.eks) != data_size) 124 { 125 GNUNET_break_op (0); 126 tsr.http_status = 0; 127 tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 128 break; 129 } 130 /* Success, call callback with all details! */ 131 memcpy (&tsr.details.success.eks, 132 data, 133 data_size); 134 break; 135 case MHD_HTTP_BAD_REQUEST: 136 /* This should never happen, either us or the anastasis server is buggy 137 (or API version conflict); just pass JSON reply to the application */ 138 GNUNET_break (0); 139 tsr.ec = TALER_JSON_get_error_code2 (data, 140 data_size); 141 break; 142 case MHD_HTTP_PAYMENT_REQUIRED: 143 { 144 struct TALER_MERCHANT_PayUriData pd; 145 146 if ( (NULL == tso->pay_uri) || 147 (GNUNET_OK != 148 TALER_MERCHANT_parse_pay_uri (tso->pay_uri, 149 &pd)) ) 150 { 151 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 152 "Failed to parse `%s'\n", 153 tso->pay_uri); 154 tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 155 break; 156 } 157 if (GNUNET_OK != 158 GNUNET_STRINGS_string_to_data ( 159 pd.order_id, 160 strlen (pd.order_id), 161 &tsr.details.payment_required.ps, 162 sizeof (tsr.details.payment_required.ps))) 163 { 164 GNUNET_break (0); 165 tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 166 TALER_MERCHANT_parse_pay_uri_free (&pd); 167 break; 168 } 169 tsr.details.payment_required.pd = &pd; 170 tsr.details.payment_required.payment_request = tso->pay_uri; 171 tso->cb (tso->cb_cls, 172 &tsr); 173 TALER_MERCHANT_parse_pay_uri_free (&pd); 174 ANASTASIS_truth_solve_cancel (tso); 175 return; 176 } 177 break; 178 case MHD_HTTP_FORBIDDEN: 179 tsr.ec = TALER_JSON_get_error_code2 (data, 180 data_size); 181 break; 182 case MHD_HTTP_NOT_FOUND: 183 tsr.ec = TALER_JSON_get_error_code2 (data, 184 data_size); 185 break; 186 case MHD_HTTP_REQUEST_TIMEOUT: 187 tsr.ec = TALER_JSON_get_error_code2 (data, 188 data_size); 189 break; 190 case MHD_HTTP_TOO_MANY_REQUESTS: 191 { 192 json_t *reply; 193 194 reply = json_loadb (data, 195 data_size, 196 JSON_REJECT_DUPLICATES, 197 NULL); 198 if (NULL == reply) 199 { 200 GNUNET_break_op (0); 201 tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 202 break; 203 } 204 205 { 206 struct GNUNET_JSON_Specification spec[] = { 207 GNUNET_JSON_spec_uint32 ( 208 "request_limit", 209 &tsr.details.too_many_requests.request_limit), 210 GNUNET_JSON_spec_relative_time ( 211 "request_frequency", 212 &tsr.details.too_many_requests.request_frequency), 213 GNUNET_JSON_spec_end () 214 }; 215 if (GNUNET_OK != 216 GNUNET_JSON_parse (reply, 217 spec, 218 NULL, NULL)) 219 { 220 GNUNET_break_op (0); 221 tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 222 json_decref (reply); 223 break; 224 } 225 json_decref (reply); 226 break; 227 } 228 } 229 case MHD_HTTP_INTERNAL_SERVER_ERROR: 230 /* Server had an internal issue; we should retry, but this API 231 leaves this to the application */ 232 tsr.ec = TALER_JSON_get_error_code2 (data, 233 data_size); 234 break; 235 case MHD_HTTP_BAD_GATEWAY: 236 tsr.ec = TALER_JSON_get_error_code2 (data, 237 data_size); 238 break; 239 default: 240 /* unexpected response code */ 241 tsr.ec = TALER_JSON_get_error_code2 (data, 242 data_size); 243 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 244 "Unexpected response code %u/%d to POST /truth/$TID/solve\n", 245 (unsigned int) response_code, 246 (int) tsr.ec); 247 break; 248 } 249 tso->cb (tso->cb_cls, 250 &tsr); 251 ANASTASIS_truth_solve_cancel (tso); 252 } 253 254 255 /** 256 * Patch value in @a val, replacing new line with '\0'. 257 * 258 * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. 259 */ 260 static void 261 patch_value (char *val) 262 { 263 size_t len; 264 265 /* found location URI we care about! */ 266 len = strlen (val); 267 while ( (len > 0) && 268 ( ('\n' == val[len - 1]) || 269 ('\r' == val[len - 1]) ) ) 270 { 271 len--; 272 val[len] = '\0'; 273 } 274 } 275 276 277 /** 278 * Handle HTTP header received by curl. 279 * 280 * @param buffer one line of HTTP header data 281 * @param size size of an item 282 * @param nitems number of items passed 283 * @param userdata our `struct ANASTASIS_StorePolicyOperation *` 284 * @return `size * nitems` 285 */ 286 static size_t 287 handle_header (char *buffer, 288 size_t size, 289 size_t nitems, 290 void *userdata) 291 { 292 struct ANASTASIS_TruthSolveOperation *tso = userdata; 293 size_t total = size * nitems; 294 char *ndup; 295 const char *hdr_type; 296 char *hdr_val; 297 char *sp; 298 299 ndup = GNUNET_strndup (buffer, 300 total); 301 hdr_type = strtok_r (ndup, 302 ":", 303 &sp); 304 if (NULL == hdr_type) 305 { 306 GNUNET_free (ndup); 307 return total; 308 } 309 hdr_val = strtok_r (NULL, 310 "", 311 &sp); 312 if (NULL == hdr_val) 313 { 314 GNUNET_free (ndup); 315 return total; 316 } 317 if (' ' == *hdr_val) 318 hdr_val++; 319 if (0 == strcasecmp (hdr_type, 320 ANASTASIS_HTTP_HEADER_TALER)) 321 { 322 /* found payment URI we care about! */ 323 GNUNET_free (tso->pay_uri); 324 tso->pay_uri = GNUNET_strdup (hdr_val); 325 patch_value (tso->pay_uri); 326 } 327 if (0 == strcasecmp (hdr_type, 328 MHD_HTTP_HEADER_CONTENT_TYPE)) 329 { 330 /* found location URI we care about! */ 331 GNUNET_free (tso->content_type); 332 tso->content_type = GNUNET_strdup (hdr_val); 333 patch_value (tso->content_type); 334 } 335 GNUNET_free (ndup); 336 return total; 337 } 338 339 340 struct ANASTASIS_TruthSolveOperation * 341 ANASTASIS_truth_solve ( 342 struct GNUNET_CURL_Context *ctx, 343 const char *backend_url, 344 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 345 const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, 346 const struct ANASTASIS_PaymentSecretP *payment_secret, 347 struct GNUNET_TIME_Relative timeout, 348 const struct GNUNET_HashCode *hashed_answer, 349 ANASTASIS_TruthSolveCallback cb, 350 void *cb_cls) 351 { 352 struct ANASTASIS_TruthSolveOperation *tso; 353 CURL *eh; 354 char *path; 355 unsigned long long tms; 356 json_t *body; 357 358 body = GNUNET_JSON_PACK ( 359 GNUNET_JSON_pack_data_auto ("truth_decryption_key", 360 truth_key), 361 GNUNET_JSON_pack_data_auto ("h_response", 362 hashed_answer), 363 GNUNET_JSON_pack_allow_null ( 364 GNUNET_JSON_pack_data_auto ("payment_secret", 365 payment_secret))); 366 GNUNET_assert (NULL != body); 367 368 tms = (unsigned long long) (timeout.rel_value_us 369 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 370 tso = GNUNET_new (struct ANASTASIS_TruthSolveOperation); 371 tso->cb = cb; 372 tso->cb_cls = cb_cls; 373 { 374 char *uuid_str; 375 376 uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, 377 sizeof (*truth_uuid)); 378 GNUNET_asprintf (&path, 379 "truth/%s/solve", 380 uuid_str); 381 GNUNET_free (uuid_str); 382 } 383 { 384 char timeout_ms[32]; 385 386 GNUNET_snprintf (timeout_ms, 387 sizeof (timeout_ms), 388 "%llu", 389 tms); 390 tso->url = TALER_url_join (backend_url, 391 path, 392 "timeout_ms", 393 (! GNUNET_TIME_relative_is_zero (timeout)) 394 ? timeout_ms 395 : NULL, 396 NULL); 397 } 398 GNUNET_free (path); 399 eh = ANASTASIS_curl_easy_get_ (tso->url); 400 if ( (NULL == eh) || 401 (GNUNET_OK != 402 TALER_curl_easy_post (&tso->ctx, 403 eh, 404 body)) ) 405 { 406 GNUNET_break (0); 407 if (NULL != eh) 408 curl_easy_cleanup (eh); 409 json_decref (body); 410 GNUNET_free (tso->url); 411 GNUNET_free (tso); 412 return NULL; 413 } 414 json_decref (body); 415 if (0 != tms) 416 GNUNET_assert (CURLE_OK == 417 curl_easy_setopt (eh, 418 CURLOPT_TIMEOUT_MS, 419 (long) (tms + 5000))); 420 GNUNET_assert (CURLE_OK == 421 curl_easy_setopt (eh, 422 CURLOPT_HEADERFUNCTION, 423 &handle_header)); 424 GNUNET_assert (CURLE_OK == 425 curl_easy_setopt (eh, 426 CURLOPT_HEADERDATA, 427 tso)); 428 tso->job = GNUNET_CURL_job_add_raw (ctx, 429 eh, 430 tso->ctx.headers, 431 &handle_truth_solve_finished, 432 tso); 433 return tso; 434 } 435 436 437 /* end of anastasis_api_truth_solve.c */