sync_api_block_operation_common.c (15312B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with TALER; see the file COPYING.LGPL. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file lib/sync_api_block_operation_common.c 21 * @brief Shared implementation for block append, update, and delete 22 * @author Iván Ávalos 23 */ 24 #include "sync_api_block_operation_common.h" 25 26 27 char * 28 SYNC_block_operation_make_url_ ( 29 const char *base_url, 30 const struct SYNC_AccountPrivateKeyP *priv, 31 const char *collection, 32 size_t id_size, 33 const void *id) 34 { 35 struct SYNC_AccountPublicKeyP pub; 36 char *pub_s; 37 char *id_s; 38 char *path; 39 char *url; 40 41 GNUNET_CRYPTO_eddsa_key_get_public (&priv->eddsa_priv, 42 &pub.eddsa_pub); 43 pub_s = GNUNET_STRINGS_data_to_string_alloc (&pub, 44 sizeof (pub)); 45 id_s = GNUNET_STRINGS_data_to_string_alloc (id, 46 id_size); 47 GNUNET_asprintf (&path, 48 "backups/%s/%s/%s", 49 pub_s, 50 collection, 51 id_s); 52 GNUNET_free (pub_s); 53 GNUNET_free (id_s); 54 url = TALER_url_join (base_url, 55 path, 56 NULL); 57 GNUNET_free (path); 58 return url; 59 } 60 61 62 void 63 SYNC_block_operation_add_etag_ ( 64 struct SYNC_BlockOperation *op, 65 const char *header, 66 const struct GNUNET_HashCode *hash) 67 { 68 char *val; 69 char *hdr; 70 71 val = GNUNET_STRINGS_data_to_string_alloc (hash, 72 sizeof (*hash)); 73 GNUNET_asprintf (&hdr, 74 "%s: \"%s\"", 75 header, 76 val); 77 GNUNET_free (val); 78 op->post_ctx.headers = curl_slist_append (op->post_ctx.headers, 79 hdr); 80 GNUNET_free (hdr); 81 } 82 83 84 /** 85 * Handle HTTP header received by curl. 86 * 87 * @param buffer one line of HTTP header data 88 * @param size size of an item 89 * @param nitems number of items passed 90 * @param userdata our `struct SYNC_BlockOperation *` 91 * @return `size * nitems` 92 */ 93 size_t 94 SYNC_block_operation_handle_header_ (char *buffer, 95 size_t size, 96 size_t nitems, 97 void *userdata) 98 { 99 struct SYNC_BlockOperation *op = userdata; 100 size_t total = size * nitems; 101 char *ndup; 102 const char *hdr_type; 103 char *hdr_val; 104 char *sp; 105 106 ndup = GNUNET_strndup (buffer, 107 total); 108 hdr_type = strtok_r (ndup, 109 ":", 110 &sp); 111 if (NULL == hdr_type) 112 { 113 GNUNET_free (ndup); 114 return total; 115 } 116 hdr_val = strtok_r (NULL, 117 "", 118 &sp); 119 if (NULL == hdr_val) 120 { 121 GNUNET_free (ndup); 122 return total; 123 } 124 if (' ' == *hdr_val) 125 hdr_val++; 126 if (0 == strcasecmp (hdr_type, 127 "Taler")) 128 { 129 size_t len; 130 131 /* A second "Taler:" header would leak the first one */ 132 GNUNET_free (op->pay_uri); 133 op->pay_uri = GNUNET_strdup (hdr_val); 134 len = strlen (op->pay_uri); 135 while ( (len > 0) && 136 ( ('\n' == op->pay_uri[len - 1]) || 137 ('\r' == op->pay_uri[len - 1]) ) ) 138 { 139 len--; 140 op->pay_uri[len] = '\0'; 141 } 142 } 143 GNUNET_free (ndup); 144 return total; 145 } 146 147 148 /** 149 * Function called when we're done processing the 150 * HTTP /backups/$KEY/blocks/$NONCE request. 151 * 152 * @param cls the `struct SYNC_BlockOperation` 153 * @param response_code HTTP response code, 0 on error 154 * @param response response body parsed as JSON, or NULL 155 */ 156 void 157 SYNC_block_operation_handle_finished_ (void *cls, 158 long response_code, 159 const void *response) 160 { 161 struct SYNC_BlockOperation *op = cls; 162 const json_t *json = response; 163 struct SYNC_BlockOperationDetails ud = { 164 .http_status = (unsigned int) response_code, 165 .ec = TALER_EC_INVALID 166 }; 167 168 op->job = NULL; 169 switch (response_code) 170 { 171 case 0: 172 break; 173 case MHD_HTTP_NO_CONTENT: 174 ud.bos = SYNC_BOS_SUCCESS; 175 ud.ec = TALER_EC_NONE; 176 break; 177 case MHD_HTTP_NOT_MODIFIED: 178 ud.bos = SYNC_BOS_NOT_MODIFIED; 179 ud.ec = TALER_EC_NONE; 180 break; 181 case MHD_HTTP_PAYMENT_REQUIRED: 182 ud.bos = SYNC_BOS_PAYMENT_REQUIRED; 183 ud.pay_uri = op->pay_uri; 184 ud.ec = TALER_EC_NONE; 185 break; 186 case MHD_HTTP_BAD_REQUEST: 187 ud.ec = (NULL != json) 188 ? TALER_JSON_get_error_code (json) 189 : TALER_EC_GENERIC_PARAMETER_MALFORMED; 190 break; 191 case MHD_HTTP_FORBIDDEN: 192 ud.ec = (NULL != json) 193 ? TALER_JSON_get_error_code (json) 194 : TALER_EC_SYNC_INVALID_SIGNATURE; 195 break; 196 case MHD_HTTP_CONTENT_TOO_LARGE: 197 case MHD_HTTP_INTERNAL_SERVER_ERROR: 198 default: 199 if ( (response_code >= 400) && 200 (response_code < 500) ) 201 ud.bos = SYNC_BOS_CLIENT_ERROR; 202 else if (response_code >= 500) 203 ud.bos = SYNC_BOS_SERVER_ERROR; 204 else 205 ud.bos = SYNC_BOS_HTTP_ERROR; 206 ud.ec = (NULL != json) 207 ? TALER_JSON_get_error_code (json) 208 : TALER_EC_GENERIC_INVALID_RESPONSE; 209 break; 210 } 211 if (NULL != op->cb) 212 { 213 op->cb (op->cb_cls, 214 &ud); 215 op->cb = NULL; 216 } 217 SYNC_block_operation_cancel (op); 218 } 219 220 221 /** 222 * Build the upload signature covering the relink of a block. 223 * 224 * A relink never changes the data or the references, so the signature 225 * covers the block's stored hash and refs_hash on both sides and its 226 * new links (the nonces of the previous and next blocks). 227 * 228 * @param priv private key of the account 229 * @param entry the block being relinked (from a block list) 230 * @param new_prev the block's new predecessor, NULL for none 231 * @param new_next the block's new successor, NULL for none 232 * @param[out] sig signature to create 233 */ 234 static void 235 relink_sign (const struct SYNC_AccountPrivateKeyP *priv, 236 const struct SYNC_BlockListEntry *entry, 237 const struct SYNC_BlockNonce *new_prev, 238 const struct SYNC_BlockNonce *new_next, 239 struct SYNC_AccountSignatureP *sig) 240 { 241 SYNC_block_upload_sign (priv, 242 &entry->nonce, 243 new_prev, 244 new_next, 245 &entry->hash, 246 &entry->hash, 247 &entry->refs_hash, 248 sig); 249 } 250 251 252 /** 253 * Serialize a relink object (relink_prev/relink_next): the new 254 * signature of the relinked neighbour. 255 * 256 * @param sig upload signature covering the relink 257 * @return JSON object to embed in the request body 258 */ 259 static json_t * 260 relink_serialize (const struct SYNC_AccountSignatureP *sig) 261 { 262 return GNUNET_JSON_PACK ( 263 GNUNET_JSON_pack_data_auto ("upload_sig", 264 sig)); 265 } 266 267 268 struct SYNC_BlockOperation * 269 SYNC_block_operation_internal_ ( 270 struct GNUNET_CURL_Context *ctx, 271 const char *base_url, 272 const struct SYNC_AccountPrivateKeyP *priv, 273 const struct SYNC_BlockOperationArgs *args, 274 SYNC_BlockOperationCallback cb, 275 void *cb_cls, 276 bool is_delete) 277 { 278 struct SYNC_BlockOperation *op; 279 struct SYNC_AccountSignatureP account_sig; 280 struct SYNC_AccountSignatureP relink_sig; 281 struct GNUNET_HashCode new_data_hash; 282 struct GNUNET_HashCode refs_hash; 283 CURL *eh; 284 json_t *req; 285 286 if (NULL != args->block_data) 287 { 288 /* Compute hash of block data */ 289 GNUNET_CRYPTO_hash (args->block_data, 290 args->block_data_size, 291 &new_data_hash); 292 } 293 294 /* Also rejects a repeated UID, which the wire format cannot carry */ 295 if (GNUNET_OK != 296 SYNC_object_refs_hash (args->refs_len, 297 args->object_uids, 298 args->object_incs, 299 &refs_hash)) 300 { 301 GNUNET_break (0); 302 return NULL; 303 } 304 305 /* Build signature */ 306 if (is_delete) 307 { 308 SYNC_block_delete_sign (priv, 309 args->nonce, 310 args->prev_nonce, 311 args->next_nonce, 312 args->block_hash, 313 &refs_hash, 314 &account_sig); 315 } 316 else 317 { 318 SYNC_block_upload_sign (priv, 319 args->nonce, 320 args->prev_nonce, 321 args->next_nonce, 322 args->old_data_hash, 323 &new_data_hash, 324 &refs_hash, 325 &account_sig); 326 } 327 328 /* Allocate operation handle */ 329 op = GNUNET_new (struct SYNC_BlockOperation); 330 op->ctx = ctx; 331 op->cb = cb; 332 op->cb_cls = cb_cls; 333 op->post_ctx.disable_compression = true; 334 op->url = SYNC_block_operation_make_url_ (base_url, 335 priv, 336 "blocks", 337 sizeof (*args->nonce), 338 args->nonce); 339 340 /* Build JSON body */ 341 if (is_delete) 342 { 343 json_t *relink_prev = NULL; 344 json_t *relink_next = NULL; 345 346 if (NULL != args->prev_entry) 347 { 348 relink_sign (priv, 349 args->prev_entry, 350 args->prev_entry->have_prev_nonce 351 ? &args->prev_entry->prev_nonce 352 : NULL, 353 args->next_nonce, 354 &relink_sig); 355 relink_prev = relink_serialize (&relink_sig); 356 } 357 358 if (NULL != args->next_entry) 359 { 360 relink_sign (priv, 361 args->next_entry, 362 args->prev_nonce, 363 args->next_entry->have_next_nonce 364 ? &args->next_entry->next_nonce 365 : NULL, 366 &relink_sig); 367 relink_next = relink_serialize (&relink_sig); 368 } 369 370 req = GNUNET_JSON_PACK ( 371 GNUNET_JSON_pack_data_auto ("delete_sig", 372 &account_sig), 373 GNUNET_JSON_pack_allow_null ( 374 GNUNET_JSON_pack_data_auto ("prev_nonce", 375 args->prev_nonce)), 376 GNUNET_JSON_pack_allow_null ( 377 GNUNET_JSON_pack_data_auto ("next_nonce", 378 args->next_nonce)), 379 GNUNET_JSON_pack_allow_null ( 380 GNUNET_JSON_pack_object_steal ( 381 "object_refs", 382 SYNC_object_refs_serialize (args->refs_len, 383 args->object_uids, 384 args->object_incs))), 385 GNUNET_JSON_pack_allow_null ( 386 GNUNET_JSON_pack_object_steal ("relink_prev", 387 relink_prev)), 388 GNUNET_JSON_pack_allow_null ( 389 GNUNET_JSON_pack_object_steal ("relink_next", 390 relink_next))); 391 } 392 else 393 { 394 json_t *relink_prev = NULL; 395 396 if (NULL != args->prev_entry) 397 { 398 relink_sign (priv, 399 args->prev_entry, 400 args->prev_entry->have_prev_nonce 401 ? &args->prev_entry->prev_nonce 402 : NULL, 403 args->nonce, 404 &relink_sig); 405 relink_prev = relink_serialize (&relink_sig); 406 } 407 408 req = GNUNET_JSON_PACK ( 409 GNUNET_JSON_pack_data_auto ("upload_sig", 410 &account_sig), 411 GNUNET_JSON_pack_allow_null ( 412 GNUNET_JSON_pack_data_auto ("prev_nonce", 413 args->prev_nonce)), 414 GNUNET_JSON_pack_allow_null ( 415 GNUNET_JSON_pack_data_auto ("next_nonce", 416 args->next_nonce)), 417 GNUNET_JSON_pack_data_varsize ("data", 418 args->block_data, 419 args->block_data_size), 420 GNUNET_JSON_pack_allow_null ( 421 GNUNET_JSON_pack_object_steal ( 422 "object_refs", 423 SYNC_object_refs_serialize (args->refs_len, 424 args->object_uids, 425 args->object_incs))), 426 GNUNET_JSON_pack_allow_null ( 427 GNUNET_JSON_pack_object_steal ("relink_prev", 428 relink_prev))); 429 } 430 431 /* Set up CURL easy handle */ 432 eh = SYNC_curl_easy_get_ (op->url); 433 if (NULL == eh) 434 { 435 GNUNET_break (0); 436 json_decref (req); 437 goto cleanup; 438 } 439 GNUNET_assert (CURLE_OK == 440 curl_easy_setopt (eh, 441 CURLOPT_HEADERFUNCTION, 442 &SYNC_block_operation_handle_header_)); 443 GNUNET_assert (CURLE_OK == 444 curl_easy_setopt (eh, 445 CURLOPT_HEADERDATA, 446 op)); 447 448 if (GNUNET_OK != 449 TALER_curl_easy_post (&op->post_ctx, 450 eh, 451 req)) 452 { 453 GNUNET_break (0); 454 json_decref (req); 455 curl_easy_cleanup (eh); 456 goto cleanup; 457 } 458 json_decref (req); 459 460 /* Override method to PUT/DELETE as needed */ 461 if (! is_delete && NULL != args->old_data_hash) 462 { 463 GNUNET_assert (CURLE_OK == 464 curl_easy_setopt (eh, 465 CURLOPT_CUSTOMREQUEST, 466 "PUT")); 467 } 468 if (is_delete) 469 { 470 GNUNET_assert (CURLE_OK == 471 curl_easy_setopt (eh, 472 CURLOPT_CUSTOMREQUEST, 473 "DELETE")); 474 } 475 476 SYNC_block_operation_add_etag_ (op, 477 MHD_HTTP_HEADER_IF_NONE_MATCH, 478 &new_data_hash); 479 /* Add If-Match header for updates and deletes */ 480 if (NULL != args->old_data_hash) 481 SYNC_block_operation_add_etag_ (op, 482 MHD_HTTP_HEADER_IF_MATCH, 483 args->old_data_hash); 484 if (is_delete) 485 SYNC_block_operation_add_etag_ (op, 486 MHD_HTTP_HEADER_IF_MATCH, 487 args->block_hash); 488 489 op->job = GNUNET_CURL_job_add2 (ctx, 490 eh, 491 op->post_ctx.headers, 492 &SYNC_block_operation_handle_finished_, 493 op); 494 return op; 495 496 cleanup: 497 GNUNET_free (op->url); 498 GNUNET_free (op); 499 return NULL; 500 } 501 502 503 void 504 SYNC_block_operation_cancel (struct SYNC_BlockOperation *op) 505 { 506 if (NULL != op->job) 507 { 508 GNUNET_CURL_job_cancel (op->job); 509 op->job = NULL; 510 } 511 TALER_curl_easy_post_finished (&op->post_ctx); 512 GNUNET_free (op->pay_uri); 513 GNUNET_free (op->url); 514 GNUNET_free (op); 515 } 516 517 518 /* end of sync_api_block_operation_common.c */