/*
This file is part of TALER
Copyright (C) 2019 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file sync-httpd_backup_post.c
* @brief functions to handle incoming requests for backups
* @author Christian Grothoff
*/
#include "platform.h"
#include "sync-httpd.h"
#include
#include "sync-httpd_backup.h"
#include
#include
#include
/**
* How long do we hold an HTTP client connection if
* we are awaiting payment before giving up?
*/
#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 30)
/**
* Context for an upload operation.
*/
struct BackupContext
{
/**
* Context for cleanup logic.
*/
struct TM_HandlerContext hc;
/**
* Signature of the account holder.
*/
struct SYNC_AccountSignatureP account_sig;
/**
* Public key of the account holder.
*/
struct SYNC_AccountPublicKeyP account;
/**
* Hash of the previous upload, or zeros if first upload.
*/
struct GNUNET_HashCode old_backup_hash;
/**
* Hash of the upload we are receiving right now (as promised
* by the client, to be verified!).
*/
struct GNUNET_HashCode new_backup_hash;
/**
* Claim token, all zeros if not known. Only set if @e existing_order_id is non-NULL.
*/
struct TALER_ClaimTokenP token;
/**
* Hash context for the upload.
*/
struct GNUNET_HashContext *hash_ctx;
/**
* Kept in DLL for shutdown handling while suspended.
*/
struct BackupContext *next;
/**
* Kept in DLL for shutdown handling while suspended.
*/
struct BackupContext *prev;
/**
* Used while suspended for resumption.
*/
struct MHD_Connection *con;
/**
* Upload, with as many bytes as we have received so far.
*/
char *upload;
/**
* Used while we are awaiting proposal creation.
*/
struct TALER_MERCHANT_PostOrdersHandle *po;
/**
* Used while we are waiting payment.
*/
struct TALER_MERCHANT_OrderMerchantGetHandle *omgh;
/**
* HTTP response code to use on resume, if non-NULL.
*/
struct MHD_Response *resp;
/**
* Order under which the client promised payment, or NULL.
*/
const char *order_id;
/**
* Order ID for the client that we found in our database.
*/
char *existing_order_id;
/**
* Timestamp of the order in @e existing_order_id. Used to
* select the most recent unpaid offer.
*/
struct GNUNET_TIME_Timestamp existing_order_timestamp;
/**
* Expected total upload size.
*/
size_t upload_size;
/**
* Current offset for the upload.
*/
size_t upload_off;
/**
* HTTP response code to use on resume, if resp is set.
*/
unsigned int response_code;
/**
* Do not look for an existing order, force a fresh order to be created.
*/
bool force_fresh_order;
};
/**
* Kept in DLL for shutdown handling while suspended.
*/
static struct BackupContext *bc_head;
/**
* Kept in DLL for shutdown handling while suspended.
*/
static struct BackupContext *bc_tail;
/**
* Service is shutting down, resume all MHD connections NOW.
*/
void
SH_resume_all_bc ()
{
struct BackupContext *bc;
while (NULL != (bc = bc_head))
{
GNUNET_CONTAINER_DLL_remove (bc_head,
bc_tail,
bc);
MHD_resume_connection (bc->con);
if (NULL != bc->po)
{
TALER_MERCHANT_orders_post_cancel (bc->po);
bc->po = NULL;
}
if (NULL != bc->omgh)
{
TALER_MERCHANT_merchant_order_get_cancel (bc->omgh);
bc->omgh = NULL;
}
}
}
/**
* Function called to clean up a backup context.
*
* @param hc a `struct BackupContext`
*/
static void
cleanup_ctx (struct TM_HandlerContext *hc)
{
struct BackupContext *bc = (struct BackupContext *) hc;
if (NULL != bc->po)
TALER_MERCHANT_orders_post_cancel (bc->po);
if (NULL != bc->hash_ctx)
GNUNET_CRYPTO_hash_context_abort (bc->hash_ctx);
if (NULL != bc->resp)
MHD_destroy_response (bc->resp);
GNUNET_free (bc->existing_order_id);
GNUNET_free (bc->upload);
GNUNET_free (bc);
}
/**
* Transmit a payment request for @a order_id on @a connection
*
* @param order_id our backend's order ID
* @param token the claim token generated by the merchant (NULL if
* it wasn't generated).
* @return MHD response to use
*/
static struct MHD_Response *
make_payment_request (const char *order_id,
const struct TALER_ClaimTokenP *token)
{
struct MHD_Response *resp;
/* request payment via Taler */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Creating payment request for order `%s'\n",
order_id);
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
TALER_MHD_add_global_headers (resp);
{
char *hdr;
char *pfx;
char *hn;
struct GNUNET_Buffer hdr_buf = { 0 };
if (0 == strncasecmp ("https://",
SH_backend_url,
strlen ("https://")))
{
pfx = "taler://";
hn = &SH_backend_url[strlen ("https://")];
}
else if (0 == strncasecmp ("http://",
SH_backend_url,
strlen ("http://")))
{
pfx = "taler+http://";
hn = &SH_backend_url[strlen ("http://")];
}
else
{
GNUNET_break (0);
MHD_destroy_response (resp);
return NULL;
}
if (0 == strlen (hn))
{
GNUNET_break (0);
MHD_destroy_response (resp);
return NULL;
}
GNUNET_buffer_write_str (&hdr_buf, pfx);
GNUNET_buffer_write_str (&hdr_buf, "pay/");
GNUNET_buffer_write_str (&hdr_buf, hn);
GNUNET_buffer_write_path (&hdr_buf, order_id);
/* No session ID */
GNUNET_buffer_write_path (&hdr_buf, "");
if (NULL != token)
{
GNUNET_buffer_write_str (&hdr_buf, "?c=");
GNUNET_buffer_write_data_encoded (&hdr_buf, token, sizeof (*token));
}
hdr = GNUNET_buffer_reap_str (&hdr_buf);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
"Taler",
hdr));
GNUNET_free (hdr);
}
return resp;
}
/**
* Callbacks of this type are used to serve the result of submitting a
* /contract request to a merchant.
*
* @param cls our `struct BackupContext`
* @param por response details
*/
static void
proposal_cb (void *cls,
const struct TALER_MERCHANT_PostOrdersReply *por)
{
struct BackupContext *bc = cls;
enum SYNC_DB_QueryStatus qs;
bc->po = NULL;
GNUNET_CONTAINER_DLL_remove (bc_head,
bc_tail,
bc);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resuming connection with order `%s'\n",
bc->order_id);
MHD_resume_connection (bc->con);
SH_trigger_daemon ();
if (MHD_HTTP_OK != por->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Backend returned status %u/%u\n",
por->hr.http_status,
(unsigned int) por->hr.ec);
GNUNET_break_op (0);
bc->resp = TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR),
GNUNET_JSON_pack_uint64 ("backend-ec",
por->hr.ec),
GNUNET_JSON_pack_uint64 ("backend-http-status",
por->hr.http_status),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("backend-reply",
(json_t *) por->hr.reply)));
bc->response_code = MHD_HTTP_BAD_GATEWAY;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Storing payment request for order `%s'\n",
por->details.ok.order_id);
qs = db->store_payment_TR (db->cls,
&bc->account,
por->details.ok.order_id,
por->details.ok.token,
&SH_annual_fee);
if (0 >= qs)
{
GNUNET_break (0);
bc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"Failed to persist payment request in sync database");
GNUNET_assert (NULL != bc->resp);
bc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Obtained fresh order `%s'\n",
por->details.ok.order_id);
bc->resp = make_payment_request (por->details.ok.order_id,
por->details.ok.token);
GNUNET_assert (NULL != bc->resp);
bc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
}
/**
* Function called on all pending payments for the right
* account.
*
* @param cls closure, our `struct BackupContext`
* @param timestamp for how long have we been waiting
* @param order_id order id in the backend
* @param token claim token to use (or NULL for none)
* @param amount how much is the order for
*/
static void
ongoing_payment_cb (void *cls,
struct GNUNET_TIME_Timestamp timestamp,
const char *order_id,
const struct TALER_ClaimTokenP *token,
const struct TALER_Amount *amount)
{
struct BackupContext *bc = cls;
(void) amount;
if (0 != TALER_amount_cmp (amount,
&SH_annual_fee))
return; /* can't re-use, fees changed */
if ( (NULL == bc->existing_order_id) ||
(GNUNET_TIME_timestamp_cmp (bc->existing_order_timestamp,
<,
timestamp)) )
{
GNUNET_free (bc->existing_order_id);
bc->existing_order_id = GNUNET_strdup (order_id);
bc->existing_order_timestamp = timestamp;
if (NULL != token)
bc->token = *token;
}
}
/**
* Callback to process a GET /check-payment request
*
* @param cls our `struct BackupContext`
* @param osr order status
*/
static void
check_payment_cb (void *cls,
const struct TALER_MERCHANT_OrderStatusResponse *osr)
{
struct BackupContext *bc = cls;
const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
/* refunds are not supported, verify */
bc->omgh = NULL;
GNUNET_CONTAINER_DLL_remove (bc_head,
bc_tail,
bc);
MHD_resume_connection (bc->con);
SH_trigger_daemon ();
switch (hr->http_status)
{
case 0:
/* Likely timeout, complain! */
bc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
bc->resp = TALER_MHD_make_error (
TALER_EC_SYNC_GENERIC_BACKEND_TIMEOUT,
NULL);
return;
case MHD_HTTP_OK:
break; /* handled below */
default:
/* Unexpected backend response */
bc->response_code = MHD_HTTP_BAD_GATEWAY;
bc->resp = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_uint64 ("code",
TALER_EC_SYNC_GENERIC_BACKEND_ERROR),
GNUNET_JSON_pack_string ("hint",
TALER_ErrorCode_get_hint (
TALER_EC_SYNC_GENERIC_BACKEND_ERROR)),
GNUNET_JSON_pack_uint64 ("backend-ec",
(json_int_t) hr->ec),
GNUNET_JSON_pack_uint64 ("backend-http-status",
(json_int_t) hr->http_status),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("backend-reply",
(json_t *) hr->reply)));
return;
}
GNUNET_assert (MHD_HTTP_OK == hr->http_status);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Payment status checked: %d\n",
osr->details.ok.status);
switch (osr->details.ok.status)
{
case TALER_MERCHANT_OSC_PAID:
{
enum SYNC_DB_QueryStatus qs;
qs = db->increment_lifetime_TR (db->cls,
&bc->account,
bc->order_id,
GNUNET_TIME_UNIT_YEARS); /* always annual */
if (0 <= qs)
return; /* continue as planned */
GNUNET_break (0);
bc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"increment lifetime");
GNUNET_assert (NULL != bc->resp);
bc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
return; /* continue as planned */
}
case TALER_MERCHANT_OSC_UNPAID:
case TALER_MERCHANT_OSC_CLAIMED:
break;
}
if (NULL != bc->existing_order_id)
{
/* repeat payment request */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Repeating payment request\n");
bc->resp = make_payment_request (bc->existing_order_id,
(GNUNET_YES == GNUNET_is_zero (&bc->token))
? NULL
: &bc->token);
GNUNET_assert (NULL != bc->resp);
bc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Timeout waiting for payment\n");
bc->resp = TALER_MHD_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT,
"Timeout awaiting promised payment");
GNUNET_assert (NULL != bc->resp);
bc->response_code = MHD_HTTP_REQUEST_TIMEOUT;
}
/**
* Helper function used to ask our backend to await
* a payment for the user's account.
*
* @param bc context to begin payment for.
* @param timeout when to give up trying
* @param order_id which order to check for the payment
*/
static void
await_payment (struct BackupContext *bc,
struct GNUNET_TIME_Relative timeout,
const char *order_id)
{
GNUNET_CONTAINER_DLL_insert (bc_head,
bc_tail,
bc);
MHD_suspend_connection (bc->con);
bc->order_id = order_id;
bc->omgh = TALER_MERCHANT_merchant_order_get (SH_ctx,
SH_backend_url,
order_id,
NULL /* our payments are NOT session-bound */,
timeout,
&check_payment_cb,
bc);
SH_trigger_curl ();
}
/**
* Helper function used to ask our backend to begin
* processing a payment for the user's account.
* May perform asynchronous operations by suspending the connection
* if required.
*
* @param bc context to begin payment for.
* @param pay_req #GNUNET_YES if payment was explicitly requested,
* #GNUNET_NO if payment is needed
* @return MHD status code
*/
static MHD_RESULT
begin_payment (struct BackupContext *bc,
int pay_req)
{
static const char *no_uuids[1] = { NULL };
json_t *order;
if (! bc->force_fresh_order)
{
enum GNUNET_DB_QueryStatus qs;
qs = db->lookup_pending_payments_by_account_TR (db->cls,
&bc->account,
&ongoing_payment_cb,
bc);
if (qs < 0)
{
struct MHD_Response *resp;
MHD_RESULT ret;
resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
"pending payments");
ret = MHD_queue_response (bc->con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
if (NULL != bc->existing_order_id)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Have existing order, waiting for `%s' to complete\n",
bc->existing_order_id);
await_payment (bc,
GNUNET_TIME_UNIT_ZERO /* no long polling */,
bc->existing_order_id);
return MHD_YES;
}
}
GNUNET_CONTAINER_DLL_insert (bc_head,
bc_tail,
bc);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Suspending connection while creating order at `%s'\n",
SH_backend_url);
MHD_suspend_connection (bc->con);
order = GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("amount",
&SH_annual_fee),
GNUNET_JSON_pack_string ("summary",
"annual fee for sync service"),
GNUNET_JSON_pack_string ("fulfillment_url",
SH_fulfillment_url));
bc->po = TALER_MERCHANT_orders_post2 (SH_ctx,
SH_backend_url,
order,
GNUNET_TIME_UNIT_ZERO,
NULL, /* no payment target */
0,
NULL, /* no inventory products */
0,
no_uuids, /* no uuids */
false, /* do NOT require claim token */
&proposal_cb,
bc);
SH_trigger_curl ();
json_decref (order);
return MHD_YES;
}
/**
* We got some query status from the DB. Handle the error cases.
* May perform asynchronous operations by suspending the connection
* if required.
*
* @param bc connection to handle status for
* @param qs query status to handle
* @return #MHD_YES or #MHD_NO
*/
static MHD_RESULT
handle_database_error (struct BackupContext *bc,
enum SYNC_DB_QueryStatus qs)
{
switch (qs)
{
case SYNC_DB_OLD_BACKUP_MISSING:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Update failed: no existing backup\n");
return TALER_MHD_reply_with_error (bc->con,
MHD_HTTP_NOT_FOUND,
TALER_EC_SYNC_PREVIOUS_BACKUP_UNKNOWN,
NULL);
case SYNC_DB_OLD_BACKUP_MISMATCH:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Conflict detected, returning existing backup\n");
return SH_return_backup (bc->con,
&bc->account,
MHD_HTTP_CONFLICT);
case SYNC_DB_PAYMENT_REQUIRED:
{
const char *order_id;
order_id = MHD_lookup_connection_value (bc->con,
MHD_GET_ARGUMENT_KIND,
"paying");
if (NULL == order_id)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Payment required, starting payment process\n");
return begin_payment (bc,
GNUNET_NO);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Payment required, awaiting completion of `%s'\n",
order_id);
await_payment (bc,
CHECK_PAYMENT_GENERIC_TIMEOUT,
order_id);
}
return MHD_YES;
case SYNC_DB_HARD_ERROR:
GNUNET_break (0);
return TALER_MHD_reply_with_error (bc->con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
case SYNC_DB_SOFT_ERROR:
GNUNET_break (0);
return TALER_MHD_reply_with_error (bc->con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
case SYNC_DB_NO_RESULTS:
GNUNET_assert (0);
return MHD_NO;
/* intentional fall-through! */
case SYNC_DB_ONE_RESULT:
GNUNET_assert (0);
return MHD_NO;
}
GNUNET_break (0);
return MHD_NO;
}
MHD_RESULT
SH_backup_post (struct MHD_Connection *connection,
void **con_cls,
const struct SYNC_AccountPublicKeyP *account,
const char *upload_data,
size_t *upload_data_size)
{
struct BackupContext *bc;
bc = *con_cls;
if (NULL == bc)
{
/* first call, setup internals */
bc = GNUNET_new (struct BackupContext);
bc->hc.cc = &cleanup_ctx;
bc->con = connection;
bc->account = *account;
{
const char *fresh;
fresh = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"fresh");
if (NULL != fresh)
bc->force_fresh_order = true;
}
*con_cls = bc;
/* now setup 'bc' */
{
const char *lens;
unsigned long len;
lens = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_CONTENT_LENGTH);
if ( (NULL == lens) ||
(1 !=
sscanf (lens,
"%lu",
&len)) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
(NULL == lens)
? TALER_EC_SYNC_MALFORMED_CONTENT_LENGTH
: TALER_EC_SYNC_MISSING_CONTENT_LENGTH,
lens);
}
if (len / 1024 / 1024 >= SH_upload_limit_mb)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_PAYLOAD_TOO_LARGE,
TALER_EC_SYNC_EXCESSIVE_CONTENT_LENGTH,
NULL);
}
bc->upload = GNUNET_malloc_large (len);
if (NULL == bc->upload)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"malloc");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_PAYLOAD_TOO_LARGE,
TALER_EC_SYNC_OUT_OF_MEMORY_ON_CONTENT_LENGTH,
NULL);
}
bc->upload_size = (size_t) len;
}
{
const char *im;
im = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_MATCH);
if (NULL != im)
{
if ( (2 >= strlen (im)) ||
('"' != im[0]) ||
('"' != im[strlen (im) - 1]) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (im + 1,
strlen (im) - 2,
&bc->old_backup_hash,
sizeof (bc->old_backup_hash))) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_SYNC_BAD_IF_MATCH,
NULL);
}
}
}
{
const char *sig_s;
sig_s = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
"Sync-Signature");
if ( (NULL == sig_s) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (sig_s,
strlen (sig_s),
&bc->account_sig,
sizeof (bc->account_sig))) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_SYNC_BAD_SYNC_SIGNATURE,
NULL);
}
}
{
const char *etag;
etag = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
if ( (NULL == etag) ||
(2 >= strlen (etag)) ||
('"' != etag[0]) ||
('"' != etag[strlen (etag) - 1]) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (etag + 1,
strlen (etag) - 2,
&bc->new_backup_hash,
sizeof (bc->new_backup_hash))) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_SYNC_BAD_IF_NONE_MATCH,
NULL);
}
}
/* validate signature */
{
struct SYNC_UploadSignaturePS usp = {
.purpose.size = htonl (sizeof (usp)),
.purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD),
.old_backup_hash = bc->old_backup_hash,
.new_backup_hash = bc->new_backup_hash
};
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD,
&usp,
&bc->account_sig.eddsa_sig,
&account->eddsa_pub))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_SYNC_INVALID_SIGNATURE,
NULL);
}
}
/* get ready to hash (done here as we may go async for payments next) */
bc->hash_ctx = GNUNET_CRYPTO_hash_context_start ();
/* Check database to see if the transaction is permissible */
{
struct GNUNET_HashCode hc;
enum SYNC_DB_QueryStatus qs;
qs = db->lookup_account_TR (db->cls,
account,
&hc);
if (qs < 0)
return handle_database_error (bc,
qs);
if (SYNC_DB_NO_RESULTS == qs)
memset (&hc, 0, sizeof (hc));
if (0 == GNUNET_memcmp (&hc,
&bc->new_backup_hash))
{
/* Refuse upload: we already have that backup! */
struct MHD_Response *resp;
MHD_RESULT ret;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
TALER_MHD_add_global_headers (resp);
ret = MHD_queue_response (connection,
MHD_HTTP_NOT_MODIFIED,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
if (0 != GNUNET_memcmp (&hc,
&bc->old_backup_hash))
{
/* Refuse upload: if-none-match failed! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Conflict detected, returning existing backup\n");
return SH_return_backup (connection,
account,
MHD_HTTP_CONFLICT);
}
}
/* check if the client insists on paying */
{
const char *order_req;
order_req = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"pay");
if (NULL != order_req)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Payment requested, starting payment process\n");
return begin_payment (bc,
GNUNET_YES);
}
}
/* ready to begin! */
return MHD_YES;
}
/* handle upload */
if (0 != *upload_data_size)
{
/* check MHD invariant */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Processing %u bytes of upload data\n",
(unsigned int) *upload_data_size);
GNUNET_assert (bc->upload_off + *upload_data_size <= bc->upload_size);
memcpy (&bc->upload[bc->upload_off],
upload_data,
*upload_data_size);
bc->upload_off += *upload_data_size;
GNUNET_CRYPTO_hash_context_read (bc->hash_ctx,
upload_data,
*upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
else if ( (0 == bc->upload_off) &&
(0 != bc->upload_size) &&
(NULL == bc->resp) )
{
/* wait for upload */
return MHD_YES;
}
if (NULL != bc->resp)
{
MHD_RESULT ret;
/* We generated a response asynchronously, queue that */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning asynchronously generated response with HTTP status %u\n",
bc->response_code);
ret = MHD_queue_response (connection,
bc->response_code,
bc->resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (bc->resp);
bc->resp = NULL;
return ret;
}
/* finished with upload, check hash */
{
struct GNUNET_HashCode our_hash;
GNUNET_CRYPTO_hash_context_finish (bc->hash_ctx,
&our_hash);
bc->hash_ctx = NULL;
if (0 != GNUNET_memcmp (&our_hash,
&bc->new_backup_hash))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_SYNC_INVALID_UPLOAD,
NULL);
}
}
/* store backup to database */
{
enum SYNC_DB_QueryStatus qs;
if (GNUNET_YES == GNUNET_is_zero (&bc->old_backup_hash))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Uploading first backup to account\n");
qs = db->store_backup_TR (db->cls,
account,
&bc->account_sig,
&bc->new_backup_hash,
bc->upload_size,
bc->upload);
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Uploading existing backup of account\n");
qs = db->update_backup_TR (db->cls,
account,
&bc->old_backup_hash,
&bc->account_sig,
&bc->new_backup_hash,
bc->upload_size,
bc->upload);
}
if (qs < 0)
return handle_database_error (bc,
qs);
if (0 == qs)
{
/* database says nothing actually changed, 304 (could
theoretically happen if another equivalent upload succeeded
since we last checked!) */
struct MHD_Response *resp;
MHD_RESULT ret;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
TALER_MHD_add_global_headers (resp);
ret = MHD_queue_response (connection,
MHD_HTTP_NOT_MODIFIED,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
}
/* generate main (204) standard success reply */
{
struct MHD_Response *resp;
MHD_RESULT ret;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
TALER_MHD_add_global_headers (resp);
ret = MHD_queue_response (connection,
MHD_HTTP_NO_CONTENT,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
}