exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 4206c3497d2cc0e247b6aca6e07b38b33824ee4f
parent 18904942205beaa1d2fbe8df68e5de918e18ec8f
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 24 Jun 2024 18:23:04 +0200

-towards form uploads (incomplete)

Diffstat:
Msrc/exchange/taler-exchange-httpd.c | 6++++--
Msrc/exchange/taler-exchange-httpd_kyc-upload.c | 287++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 290 insertions(+), 3 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c @@ -1892,8 +1892,10 @@ handle_mhd_request (void *cls, } /* Check if upload is in bounds */ - if (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) + if ( (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) || + (0 == strcasecmp (method, + MHD_HTTP_METHOD_PATCH)) ) { TALER_MHD_check_content_length (connection, TALER_MHD_REQUEST_BUFFER_MAX); diff --git a/src/exchange/taler-exchange-httpd_kyc-upload.c b/src/exchange/taler-exchange-httpd_kyc-upload.c @@ -21,17 +21,139 @@ #include "platform.h" #include "taler-exchange-httpd_kyc-upload.h" +/** + * Size of the MHD post processor upload buffer. Rather large, as we expect + * large documents. Note that we *additionally* do stream processing, so the + * actual uploads can be larger and are bounded (globally) by + * #TALER_MHD_REQUEST_BUFFER_MAX. + */ +#define UPLOAD_BUFFER_SIZE (1024 * 1024) + /** * Context used for processing the KYC upload req */ struct UploadContext { + /** + * Our post processor. + */ struct MHD_PostProcessor *pp; + + /** + * Uploaded data, in JSON. + */ + json_t *result; + + /** + * Last key in upload. + */ + char *last_key; + + /** + * Name of the file, possibly NULL. + */ + char *filename; + + /** + * Content type, possibly NULL. + */ + char *content_type; + + /** + * Current upload data. + */ + char *curr_buf; + + /** + * Size of @e curr_buf allocation. + */ + size_t buf_size; + + /** + * Number of bytes of actual data in @a curr_buf. + */ + size_t buf_pos; + }; /** + * Check if the upload data is in binary and thus + * must be base32-encoded. + * + * @param uc upload context with data to eval + */ +static bool +is_binary (const struct UploadContext *uc) +{ + if (NULL != memchr (uc->curr_buf, + '\0', + uc->buf_pos)) + return true; + if (NULL != uc->filename) + return true; /* we always encode all files */ + if (NULL == uc->content_type) + return false; /* fingers crossed */ + if (0 == strncmp (uc->content_type, + "text/", + strlen ("text/"))) + return false; /* good */ + return true; +} + + +/** + * Finish processing the data in @a uc under the current + * key. + * + * @param[in,out] uc upload context with key to process + */ +static void +finish_key (struct UploadContext *uc) +{ + json_t *val; + + if (NULL == uc->last_key) + return; /* nothing to do */ + if (is_binary (uc)) + { + val = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("filename", + uc->filename)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("content_type", + uc->content_type)), + GNUNET_JSON_pack_data_varsize ("data", + uc->curr_buf, + uc->buf_pos) + ); + } + else + { + val = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("content_type", + uc->content_type)), + GNUNET_JSON_pack_string ("text", + uc->curr_buf)); + } + GNUNET_assert (0 == + json_object_set_new (uc->result, + uc->last_key, + val)); + GNUNET_free (uc->last_key); + GNUNET_free (uc->filename); + GNUNET_free (uc->content_type); + memset (uc->curr_buf, + 0, + uc->buf_pos); + uc->buf_pos = 0; +} + + +/** * Function called to clean up upload context. */ static void @@ -39,10 +161,89 @@ upload_cleaner (struct TEH_RequestContext *rc) { struct UploadContext *uc = rc->rh_ctx; + MHD_destroy_post_processor (uc->pp); + GNUNET_free (uc->filename); + GNUNET_free (uc->content_type); + GNUNET_free (uc->last_key); + GNUNET_free (uc->curr_buf); + json_decref (uc->result); GNUNET_free (uc); } +/** + * Iterator over key-value pairs where the value + * may be made available in increments and/or may + * not be zero-terminated. Used for processing + * POST data. + * + * @param cls user-specified closure + * @param kind type of the value, always #MHD_POSTDATA_KIND when called from MHD + * @param key 0-terminated key for the value, NULL if not known. This value + * is never NULL for url-encoded POST data. + * @param filename name of the uploaded file, NULL if not known + * @param content_type mime-type of the data, NULL if not known + * @param transfer_encoding encoding of the data, NULL if not known + * @param data pointer to @a size bytes of data at the + * specified offset + * @param off offset of data in the overall value + * @param size number of bytes in @a data available + * @return #MHD_YES to continue iterating, + * #MHD_NO to abort the iteration + */ +static enum MHD_Result +post_helper (void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *filename, + const char *content_type, + const char *transfer_encoding, + const char *data, + uint64_t off, + size_t size) +{ + struct UploadContext *uc = cls; + + if ( (NULL != uc->last_key) && + (0 != strcmp (key, + uc->last_key)) ) + finish_key (uc); + if (NULL == uc->last_key) + { + uc->last_key = GNUNET_strdup (key); + if (NULL != filename) + uc->filename = GNUNET_strdup (filename); + if (NULL != content_type) + uc->content_type = GNUNET_strdup (content_type); + } + if (size > uc->buf_size - uc->buf_pos) + { + char *tmp; + size_t tgt; + + tgt = uc->buf_size * 2; + if (tgt >= GNUNET_MAX_MALLOC_CHECKED - 1) + tgt = GNUNET_MAX_MALLOC_CHECKED - 1; + if (tgt < size + uc->buf_pos) + tgt = size + uc->buf_pos; + if (tgt >= GNUNET_MAX_MALLOC_CHECKED - 1) + return MHD_NO; + tmp = GNUNET_malloc (tgt + 1); /* for 0-termination */ + memcpy (tmp, + uc->curr_buf, + uc->buf_pos); + GNUNET_free (uc->curr_buf); + uc->buf_size = tgt; + uc->curr_buf = tmp; + } + memcpy (uc->curr_buf + uc->buf_pos, + data, + size); + uc->buf_pos += size; + return MHD_YES; +} + + MHD_RESULT TEH_handler_kyc_upload (struct TEH_RequestContext *rc, const char *id, @@ -54,8 +255,92 @@ TEH_handler_kyc_upload (struct TEH_RequestContext *rc, if (NULL == uc) { uc = GNUNET_new (struct UploadContext); + uc->pp = MHD_create_post_processor (rc->connection, + UPLOAD_BUFFER_SIZE, + &post_helper, + uc); + if (NULL == uc->pp) + { + GNUNET_break (0); + GNUNET_free (uc); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_ALLOCATION_FAILURE, + "MHD_create_post_processor"); + } + uc->result = json_object (); + GNUNET_assert (NULL != uc->result); rc->rh_ctx = uc; rc->rh_cleaner = &upload_cleaner; + return MHD_YES; + } + if (0 != *upload_data_size) + { + MHD_RESULT mres; + + mres = MHD_post_process (uc->pp, + upload_data, + *upload_data_size); + *upload_data_size = 0; + return mres; + } + finish_key (uc); + // FIXME: handle collected upload data! + { + uint64_t process_row; + struct TALER_PaytoHashP h_payto; + struct GNUNET_TIME_Timestamp now; + struct GNUNET_TIME_Absolute expiration_time; + void *enc_attributes; + size_t enc_attributes_size; + enum GNUNET_DB_QueryStatus qs; + + now = GNUNET_TIME_timestamp_get (); + + TALER_CRYPTO_kyc_attributes_encrypt ( + &TEH_attribute_key, + uc->result, + &enc_attributes, + &enc_attributes_size); + qs = TEH_plugin->insert_kyc_attributes ( + TEH_plugin->cls, + process_row, + &h_payto, + 0 /* birthday unknown */, + now, + NULL /* provider name */, + NULL /* provider account */, + NULL /* provider legi ID */, + expiration_time, + enc_attributes_size, + enc_attributes, + false /* require aml??? Pass do not know? */ + ); + GNUNET_free (enc_attributes); + if (qs < 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_kyc_attributes"); + } + if (0 == qs) + { + // FIXME: check for idempotency? + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED, + "insert_kyc_attributes"); + } } - return MHD_NO; + return TALER_MHD_reply_static ( + rc->connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); }