From a290b4b02940bd702720b469a8c88a6ee931813b Mon Sep 17 00:00:00 2001 From: Christian Blättler Date: Sun, 10 Mar 2024 14:07:24 +0100 Subject: implement parsing of choices array for v1 contracts --- .../taler-merchant-httpd_private-post-orders.c | 394 +++++++++++++-------- 1 file changed, 241 insertions(+), 153 deletions(-) (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c') diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index bea9fd51..1c25da4e 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -228,31 +228,14 @@ struct OrderContext struct { /** - * Our order ID. - */ - const char *order_id; - - /** - * Array of possible specific contracts the wallet/customer may choose - * from by selecting the respective index when signing the deposit - * confirmation. - */ - struct TALER_MerchantContractChoice *choices; - - /** - * Length of the @e choices array. - */ - unsigned int choices_len; - - /** - * Summary of the order. + * Version of the contract terms. */ - // const char *summary; + enum TALER_MerchantContractVersion version; /** - * Internationalized summary. + * Our order ID. */ - // json_t *summary_i18n; + const char *order_id; /** * URL where the same contract could be ordered again (if available). @@ -260,21 +243,9 @@ struct OrderContext const char *public_reorder_url; /** - * URL that will show that the order was successful - * after it has been paid for. - */ - // const char *fulfillment_url; - - /** - * Message shown to the customer after paying for the order. - * Either fulfillment_url or fulfillment_message must be specified. - */ - // const char *fulfillment_message; - - /** - * Map from IETF BCP 47 language tags to localized fulfillment messages. - */ - // json_t *fulfillment_message_i18n; + * Optional array of contract choices. Is null for v0 contracts. + */ + const json_t *choices; /** * Merchant base URL. @@ -311,38 +282,12 @@ struct OrderContext */ json_t *delivery_location; - /** - * Array of products that are part of the purchase. - */ - // const json_t *products; - - /** - * TODO: Maybe remove this and set it from settings where we serialize - * the order to JSON? - * - * Information like name, website, email, etc. about the merchant. - */ - // json_t *merchant; - - /** - * TODO: Maybe remove this and set it from settings where we serialize - * the order to JSON? - * - * Merchant's public key - */ - // struct TALER_MerchantPublicKeyP merchant_pub; - /** * Gross amount value of the contract. Used to * compute @e max_stefan_fee. */ struct TALER_Amount brutto; - /** - * Maximum fee as given by the client request. - */ - // struct TALER_Amount max_fee; - /** * Array of fee limits and wire account details by currency. */ @@ -372,6 +317,24 @@ struct OrderContext } parse_order; + /** + * Information set in the ORDER_PHASE_PARSE_CHOICES phase. + */ + struct + { + /** + * Array of possible specific contracts the wallet/customer may choose + * from by selecting the respective index when signing the deposit + * confirmation. + */ + struct TALER_MerchantContractChoice *choices; + + /** + * Length of the @e choices array. + */ + unsigned int choices_len; + } parse_choices; + /** * Information set in the ORDER_PHASE_MERGE_INVENTORY phase. */ @@ -432,11 +395,6 @@ struct OrderContext */ struct { - /** - * Maximum fee - */ - // struct TALER_Amount max_fee; - /** * Array of fee limits and wire account details by currency. */ @@ -500,6 +458,7 @@ struct OrderContext { ORDER_PHASE_PARSE_REQUEST, ORDER_PHASE_PARSE_ORDER, + ORDER_PHASE_PARSE_CHOICES, ORDER_PHASE_MERGE_INVENTORY, ORDER_PHASE_ADD_PAYMENT_DETAILS, ORDER_PHASE_SET_EXCHANGES, @@ -638,17 +597,17 @@ clean_order (void *cls) oc->set_exchanges.exchanges = NULL; } /* TODO: Clean choices array */ - for (unsigned int i = 0; iparse_order.choices_len; i++) + for (unsigned int i = 0; iparse_choices.choices_len; i++) { - if (NULL != oc->parse_order.choices[i].fulfillment_message_i18n) + if (NULL != oc->parse_choices.choices[i].fulfillment_message_i18n) { - json_decref (oc->parse_order.choices[i].fulfillment_message_i18n); - oc->parse_order.choices[i].fulfillment_message_i18n = NULL; + json_decref (oc->parse_choices.choices[i].fulfillment_message_i18n); + oc->parse_choices.choices[i].fulfillment_message_i18n = NULL; } - if (NULL != oc->parse_order.choices[i].summary_i18n) + if (NULL != oc->parse_choices.choices[i].summary_i18n) { - json_decref (oc->parse_order.choices[i].summary_i18n); - oc->parse_order.choices[i].summary_i18n = NULL; + json_decref (oc->parse_choices.choices[i].summary_i18n); + oc->parse_choices.choices[i].summary_i18n = NULL; } if (NULL != oc->parse_order.delivery_location) { @@ -1351,7 +1310,7 @@ serialize_order (struct OrderContext *oc) } // TODO: How to properly handle these cases / errors? - GNUNET_assert (1 == oc->parse_order.choices_len && NULL != choice); + GNUNET_assert (1 == oc->parse_choices.choices_len && NULL != choice); GNUNET_assert (1 == oc->set_max_fee.limits_len && NULL != limits); oc->serialize_order.contract = GNUNET_JSON_PACK ( @@ -1541,40 +1500,33 @@ set_exchanges (struct OrderContext *oc) /** - * Add missing fields to the order. Upon success, continue - * processing with merge_inventory(). + * Parse the order field of the request. Upon success, continue + * processing with parse_choices(). * * @param[in,out] oc order context */ static void parse_order (struct OrderContext *oc) { - struct TALER_MerchantContractChoice *choice; struct TALER_MerchantContractLimits *limits; const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; const char *merchant_base_url = NULL; + const char *version = NULL; const json_t *jmerchant = NULL; - choice = GNUNET_new (struct TALER_MerchantContractChoice); limits = GNUNET_new (struct TALER_MerchantContractLimits); /* auto_refund only needs to be type-checked, * mostly because in GNUnet relative times can't * be negative. */ bool no_fee; struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("amount", - &oc->parse_order.brutto), - GNUNET_JSON_spec_string ("summary", - &choice->summary), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("products", - &choice->products), - NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("summary_i18n", - &choice->summary_i18n), + GNUNET_JSON_spec_string ("version", + &version), NULL), + TALER_JSON_spec_amount_any ("amount", + &oc->parse_order.brutto), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("order_id", &oc->parse_order.order_id), @@ -1584,16 +1536,8 @@ parse_order (struct OrderContext *oc) &oc->parse_order.public_reorder_url), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("fulfillment_message", - &choice->fulfillment_message), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("fulfillment_message_i18n", - &choice->fulfillment_message_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("fulfillment_url", - &choice->fulfillment_url), + GNUNET_JSON_spec_array_const ("choices", + &oc->parse_order.choices), NULL), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_web_url ("merchant_base_url", @@ -1655,6 +1599,34 @@ parse_order (struct OrderContext *oc) ret); return; } + if (NULL == version || 0 == strcmp("v0", version)) + { + oc->parse_order.version = TALER_MCV_V0; + } + else if (0 != strcmp("v1", version)) + { + oc->parse_order.version = TALER_MCV_V1; + } + else + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_VERSION_MALFORMED, + "invalid version specified in order, supported are null, 'v0' or 'v1'"); + return; + } + if (NULL == oc->parse_order.choices && TALER_MCV_V0 != oc->parse_order.version) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR, + "choices array must not be null for v1 contracts"); + return; + } if (! TMH_test_exchange_configured_for_currency ( oc->parse_order.brutto.currency)) { @@ -1732,46 +1704,6 @@ parse_order (struct OrderContext *oc) GNUNET_assert (NULL != oc->parse_order.order_id); } - /* Patch fulfillment URL with order_id (implements #6467). */ - if (NULL != choice->fulfillment_url) - { - const char *pos; - - pos = strstr (choice->fulfillment_url, - "${ORDER_ID}"); - if (NULL != pos) - { - /* replace ${ORDER_ID} with the real order_id */ - char *nurl; - - /* We only allow one placeholder */ - if (strstr (pos + strlen ("${ORDER_ID}"), - "${ORDER_ID}")) - { - GNUNET_break_op (0); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "fulfillment_url"); - return; - } - - GNUNET_asprintf (&nurl, - "%.*s%s%s", - /* first output URL until ${ORDER_ID} */ - (int) (pos - choice->fulfillment_url), - choice->fulfillment_url, - /* replace ${ORDER_ID} with the right order_id */ - oc->parse_order.order_id, - /* append rest of original URL */ - pos + strlen ("${ORDER_ID}")); - - choice->fulfillment_url = GNUNET_strdup (nurl); - - GNUNET_free (nurl); - } - } - /* Check soundness of refund deadline, and that a timestamp * is actually present. */ { @@ -1914,18 +1846,6 @@ parse_order (struct OrderContext *oc) oc->parse_order.merchant_base_url = url; } - if ( (NULL != choice->products) && - (! TMH_products_array_valid (choice->products)) ) - { - GNUNET_break_op (0); - reply_with_error ( - oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "order.products"); - return; - } - /* Merchant information must not already be present */ if (NULL != jmerchant) { @@ -1952,6 +1872,171 @@ parse_order (struct OrderContext *oc) oc->phase++; } +/** + * Parse a json contract choice. + * + * @param[out] choice resulting contract choice + * @param root json object + * @param order_id order_id to be replaced in fulfillment URL + * @return #GNUNET_OK if choice could be parsed. + */ +static enum GNUNET_GenericReturnValue +parse_choice ( + struct TALER_MerchantContractChoice *choice, + json_t *root, + const char *order_id) +{ + const char *error_name; + unsigned int error_line; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("summary", + &choice->summary), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("products", + &choice->products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("summary_i18n", + &choice->summary_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("fulfillment_message", + &choice->fulfillment_message), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("fulfillment_message_i18n", + &choice->fulfillment_message_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("fulfillment_url", + &choice->fulfillment_url), + NULL), + }; + + enum GNUNET_GenericReturnValue ret; + ret = GNUNET_JSON_parse (root, + spec, + &error_name, + &error_line); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Choice parsing failed: %s:%u\n", + error_name, + error_line); + return ret; + } + + /* Patch fulfillment URL with order_id (implements #6467). */ + if (NULL != choice->fulfillment_url) + { + const char *pos; + + pos = strstr (choice->fulfillment_url, + "${ORDER_ID}"); + if (NULL != pos) + { + /* replace ${ORDER_ID} with the real order_id */ + char *nurl; + + /* We only allow one placeholder */ + if (strstr (pos + strlen ("${ORDER_ID}"), + "${ORDER_ID}")) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Patching fulfillment URL failed\n"); + return GNUNET_SYSERR; + } + + GNUNET_asprintf (&nurl, + "%.*s%s%s", + /* first output URL until ${ORDER_ID} */ + (int) (pos - choice->fulfillment_url), + choice->fulfillment_url, + /* replace ${ORDER_ID} with the right order_id */ + order_id, + /* append rest of original URL */ + pos + strlen ("${ORDER_ID}")); + + choice->fulfillment_url = GNUNET_strdup (nurl); + + GNUNET_free (nurl); + } + } + + if ( (NULL != choice->products) && + (! TMH_products_array_valid (choice->products)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid choice.products array\n"); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + +/** + * Check contract version and call parse_choices_v0 or parse_choices_v1 + * respectively. Upon success, continue processing with merge_inventory(). + * + * @param[in,out] oc order context + */ +static void +parse_choices (struct OrderContext *oc) +{ + enum GNUNET_GenericReturnValue ret; + + if (TALER_MCV_V0 == oc->parse_order.version) + { + /** + * v0 contract is basically a v1 contract with only one choice + * and no inputs or outputs. + */ + GNUNET_array_grow (oc->parse_choices.choices, + oc->parse_choices.choices_len, + 1); + + ret = parse_choice(oc->parse_choices.choices, + oc->parse_request.order, + oc->parse_order.order_id); + + if (GNUNET_OK != ret) + { + GNUNET_break_op (0); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "order"); + return; + } + } + else if (TALER_MCV_V1 == oc->parse_order.version) + { + GNUNET_assert (NULL != oc->parse_order.choices); + + GNUNET_array_grow (oc->parse_choices.choices, + oc->parse_choices.choices_len, + json_array_size (oc->parse_order.choices)); + for (unsigned int i = 0; iparse_choices.choices_len; i++) + { + ret = parse_choice(&oc->parse_choices.choices[i], + json_array_get (oc->parse_order.choices, i), + oc->parse_order.order_id); + + if (GNUNET_OK != ret) + { + GNUNET_break_op (0); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "choices"); + return; + } + } + } + + oc->phase++; +} /** * Process the @a payment_target and add the details of how the @@ -1999,9 +2084,9 @@ add_payment_details (struct OrderContext *oc) static void merge_inventory (struct OrderContext *oc) { - struct TALER_MerchantContractChoice *choice = oc->parse_order.choices; + struct TALER_MerchantContractChoice *choice = oc->parse_choices.choices; - GNUNET_assert (1 == oc->parse_order.choices_len && NULL != choice); + GNUNET_assert (1 == oc->parse_choices.choices_len && NULL != choice); /** * parse_request.inventory_products => instructions to add products to contract terms @@ -2338,6 +2423,9 @@ TMH_private_post_orders ( case ORDER_PHASE_PARSE_ORDER: parse_order (oc); break; + case ORDER_PHASE_PARSE_CHOICES: + parse_choices (oc); + break; case ORDER_PHASE_MERGE_INVENTORY: merge_inventory (oc); break; -- cgit v1.2.3