LCOV - code coverage report
Current view: top level - lib - merchant_api_get-templates-TEMPLATE_ID.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 75.6 % 156 118
Test Date: 2026-09-11 17:45:24 Functions: 100.0 % 8 8

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2022-2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Lesser General Public License as published by the Free Software
       7              :   Foundation; either version 2.1, or (at your option) any later version.
       8              : 
       9              :   TALER 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 Lesser General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU Lesser General Public License along with
      14              :   TALER; see the file COPYING.LGPL.  If not, see
      15              :   <http://www.gnu.org/licenses/>
      16              : */
      17              : /**
      18              :  * @file src/lib/merchant_api_get-templates-TEMPLATE_ID.c
      19              :  * @brief Implementation of the GET /templates/$TEMPLATE_ID request (wallet-facing)
      20              :  * @author Christian Grothoff
      21              :  */
      22              : #include "platform.h"
      23              : #include <curl/curl.h>
      24              : #include <jansson.h>
      25              : #include <microhttpd.h> /* just for HTTP status codes */
      26              : #include <gnunet/gnunet_util_lib.h>
      27              : #include <gnunet/gnunet_curl_lib.h>
      28              : #include <taler/merchant/get-templates-TEMPLATE_ID.h>
      29              : #include "merchant_api_curl_defaults.h"
      30              : #include <taler/taler_json_lib.h>
      31              : 
      32              : 
      33              : /**
      34              :  * Maximum number of exchange candidates or wire methods accepted in a reply.
      35              :  */
      36              : #define MAX_CANDIDATE_ENTRIES 1024
      37              : 
      38              : 
      39              : /**
      40              :  * Free merchant metadata parsed for a callback.
      41              :  *
      42              :  * @param merchant metadata to free
      43              :  */
      44              : static void
      45            6 : free_merchant_metadata (struct TALER_MERCHANT_MetaData *merchant)
      46              : {
      47            6 :   GNUNET_free (merchant->name);
      48            6 :   GNUNET_free (merchant->website);
      49            6 :   GNUNET_free (merchant->email);
      50            6 :   GNUNET_free (merchant->logo);
      51            6 :   if (NULL != merchant->address)
      52            6 :     json_decref (merchant->address);
      53            6 :   if (NULL != merchant->jurisdiction)
      54            6 :     json_decref (merchant->jurisdiction);
      55            6 : }
      56              : 
      57              : 
      58              : /**
      59              :  * Parse merchant metadata.
      60              :  *
      61              :  * @param object merchant JSON object
      62              :  * @param[out] merchant parsed metadata
      63              :  * @return #GNUNET_OK on success
      64              :  */
      65              : static enum GNUNET_GenericReturnValue
      66            6 : parse_merchant_metadata (const json_t *object,
      67              :                          struct TALER_MERCHANT_MetaData *merchant)
      68              : {
      69              :   struct GNUNET_JSON_Specification spec[] = {
      70            6 :     GNUNET_JSON_spec_string_copy ("name",
      71              :                                   &merchant->name),
      72            6 :     GNUNET_JSON_spec_mark_optional (
      73              :       TALER_JSON_spec_web_url_copy ("website",
      74              :                                     &merchant->website),
      75              :       NULL),
      76            6 :     GNUNET_JSON_spec_mark_optional (
      77              :       GNUNET_JSON_spec_string_copy ("email",
      78              :                                     &merchant->email),
      79              :       NULL),
      80            6 :     GNUNET_JSON_spec_mark_optional (
      81              :       GNUNET_JSON_spec_string_copy ("logo",
      82              :                                     &merchant->logo),
      83              :       NULL),
      84            6 :     GNUNET_JSON_spec_mark_optional (
      85              :       GNUNET_JSON_spec_object_copy ("address",
      86              :                                     &merchant->address),
      87              :       NULL),
      88            6 :     GNUNET_JSON_spec_mark_optional (
      89              :       GNUNET_JSON_spec_object_copy ("jurisdiction",
      90              :                                     &merchant->jurisdiction),
      91              :       NULL),
      92            6 :     GNUNET_JSON_spec_end ()
      93              :   };
      94              : 
      95            6 :   return GNUNET_JSON_parse (object,
      96              :                             spec,
      97              :                             NULL,
      98              :                             NULL);
      99              : }
     100              : 
     101              : 
     102              : /**
     103              :  * Free parsed exchange candidates.
     104              :  *
     105              :  * @param num_candidates number of candidates
     106              :  * @param candidates candidate array
     107              :  */
     108              : static void
     109            6 : free_exchange_candidates (
     110              :   unsigned int num_candidates,
     111              :   struct TALER_MERCHANT_TemplateExchangeCandidate *candidates)
     112              : {
     113           12 :   for (unsigned int i = 0; i<num_candidates; i++)
     114            6 :     GNUNET_free (candidates[i].wire_methods);
     115            6 :   GNUNET_free (candidates);
     116            6 : }
     117              : 
     118              : 
     119              : /**
     120              :  * Parse exchange candidates from a template response.
     121              :  *
     122              :  * @param array candidate JSON array
     123              :  * @param[out] num_candidates number of parsed entries
     124              :  * @param[out] candidates parsed entries
     125              :  * @return #GNUNET_OK on success
     126              :  */
     127              : static enum GNUNET_GenericReturnValue
     128            6 : parse_exchange_candidates (
     129              :   const json_t *array,
     130              :   unsigned int *num_candidates,
     131              :   struct TALER_MERCHANT_TemplateExchangeCandidate **candidates)
     132              : {
     133            6 :   size_t array_size = json_array_size (array);
     134              :   struct TALER_MERCHANT_TemplateExchangeCandidate *result;
     135              : 
     136            6 :   if ( (array_size > MAX_CANDIDATE_ENTRIES) ||
     137            6 :        (array_size != (unsigned int) array_size) )
     138            0 :     return GNUNET_SYSERR;
     139            6 :   result = GNUNET_new_array (
     140              :     array_size,
     141              :     struct TALER_MERCHANT_TemplateExchangeCandidate);
     142           12 :   for (unsigned int i = 0; i<array_size; i++)
     143              :   {
     144            6 :     struct TALER_MERCHANT_TemplateExchangeCandidate *candidate = &result[i];
     145              :     const json_t *wire_methods;
     146              :     size_t num_methods;
     147              :     const char **methods;
     148              :     struct GNUNET_JSON_Specification spec[] = {
     149            6 :       TALER_JSON_spec_web_url ("base_url",
     150              :                                &candidate->base_url),
     151            6 :       GNUNET_JSON_spec_string ("currency",
     152              :                                &candidate->currency),
     153            6 :       GNUNET_JSON_spec_fixed_auto ("master_pub",
     154              :                                    &candidate->master_pub),
     155            6 :       GNUNET_JSON_spec_array_const ("wire_methods",
     156              :                                     &wire_methods),
     157            6 :       GNUNET_JSON_spec_end ()
     158              :     };
     159              : 
     160            6 :     if (GNUNET_OK !=
     161            6 :         GNUNET_JSON_parse (json_array_get (array,
     162              :                                            i),
     163              :                            spec,
     164              :                            NULL,
     165              :                            NULL))
     166            0 :       goto fail;
     167            6 :     num_methods = json_array_size (wire_methods);
     168            6 :     if ( (0 == num_methods) ||
     169            6 :          (num_methods > MAX_CANDIDATE_ENTRIES) ||
     170            6 :          (num_methods != (unsigned int) num_methods) )
     171            0 :       goto fail;
     172            6 :     methods = GNUNET_new_array (num_methods,
     173              :                                 const char *);
     174           12 :     for (unsigned int j = 0; j<num_methods; j++)
     175              :     {
     176            6 :       const json_t *method = json_array_get (wire_methods,
     177              :                                              j);
     178              : 
     179            6 :       if (! json_is_string (method))
     180              :       {
     181            0 :         GNUNET_free (methods);
     182            0 :         goto fail;
     183              :       }
     184            6 :       methods[j] = json_string_value (method);
     185              :     }
     186            6 :     candidate->num_wire_methods = (unsigned int) num_methods;
     187            6 :     candidate->wire_methods = methods;
     188              :   }
     189            6 :   *num_candidates = (unsigned int) array_size;
     190            6 :   *candidates = result;
     191            6 :   return GNUNET_OK;
     192              : 
     193            0 : fail:
     194            0 :   free_exchange_candidates ((unsigned int) array_size,
     195              :                             result);
     196            0 :   return GNUNET_SYSERR;
     197              : }
     198              : 
     199              : 
     200              : /**
     201              :  * Handle for a GET /templates/$TEMPLATE_ID operation (wallet-facing).
     202              :  */
     203              : struct TALER_MERCHANT_GetTemplatesHandle
     204              : {
     205              :   /**
     206              :    * Base URL of the merchant backend.
     207              :    */
     208              :   char *base_url;
     209              : 
     210              :   /**
     211              :    * The full URL for this request.
     212              :    */
     213              :   char *url;
     214              : 
     215              :   /**
     216              :    * Handle for the request.
     217              :    */
     218              :   struct GNUNET_CURL_Job *job;
     219              : 
     220              :   /**
     221              :    * Function to call with the result.
     222              :    */
     223              :   TALER_MERCHANT_GetTemplatesCallback cb;
     224              : 
     225              :   /**
     226              :    * Closure for @a cb.
     227              :    */
     228              :   TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE *cb_cls;
     229              : 
     230              :   /**
     231              :    * Reference to the execution context.
     232              :    */
     233              :   struct GNUNET_CURL_Context *ctx;
     234              : 
     235              :   /**
     236              :    * Template identifier.
     237              :    */
     238              :   char *template_id;
     239              : };
     240              : 
     241              : 
     242              : /**
     243              :  * Function called when we're done processing the
     244              :  * HTTP GET /templates/$TEMPLATE_ID request.
     245              :  *
     246              :  * @param cls the `struct TALER_MERCHANT_GetTemplatesHandle`
     247              :  * @param response_code HTTP response code, 0 on error
     248              :  * @param response response body, NULL if not in JSON
     249              :  */
     250              : static void
     251            6 : handle_get_template_finished (void *cls,
     252              :                               long response_code,
     253              :                               const void *response)
     254              : {
     255            6 :   struct TALER_MERCHANT_GetTemplatesHandle *gth = cls;
     256            6 :   const json_t *json = response;
     257            6 :   struct TALER_MERCHANT_MetaData merchant = { 0 };
     258              :   struct TALER_MerchantPublicKeyP merchant_pub;
     259            6 :   struct TALER_MERCHANT_TemplateExchangeCandidate *candidates = NULL;
     260            6 :   unsigned int num_candidates = 0;
     261            6 :   struct TALER_MERCHANT_GetTemplatesResponse wtgr = {
     262            6 :     .hr.http_status = (unsigned int) response_code,
     263              :     .hr.reply = json
     264              :   };
     265              : 
     266            6 :   gth->job = NULL;
     267            6 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     268              :               "Got /templates/$TEMPLATE_ID response with status code %u\n",
     269              :               (unsigned int) response_code);
     270            6 :   switch (response_code)
     271              :   {
     272            6 :   case MHD_HTTP_OK:
     273              :     {
     274            6 :       const json_t *exchange_candidates = NULL;
     275            6 :       const json_t *merchant_object = NULL;
     276              :       bool exchange_candidates_missing;
     277              :       bool merchant_missing;
     278              :       bool merchant_pub_missing;
     279              :       struct GNUNET_JSON_Specification spec[] = {
     280            6 :         GNUNET_JSON_spec_object_const ("template_contract",
     281              :                                        &wtgr.details.ok.template_contract),
     282            6 :         GNUNET_JSON_spec_mark_optional (
     283              :           GNUNET_JSON_spec_object_const (
     284              :             "editable_defaults",
     285              :             &wtgr.details.ok.editable_defaults),
     286              :           NULL),
     287            6 :         GNUNET_JSON_spec_mark_optional (
     288              :           GNUNET_JSON_spec_string (
     289              :             "required_currency",
     290              :             &wtgr.details.ok.required_currency),
     291              :           NULL),
     292            6 :         GNUNET_JSON_spec_mark_optional (
     293              :           GNUNET_JSON_spec_object_const ("merchant",
     294              :                                          &merchant_object),
     295              :           &merchant_missing),
     296            6 :         GNUNET_JSON_spec_mark_optional (
     297              :           GNUNET_JSON_spec_fixed_auto ("merchant_pub",
     298              :                                        &merchant_pub),
     299              :           &merchant_pub_missing),
     300            6 :         GNUNET_JSON_spec_mark_optional (
     301              :           GNUNET_JSON_spec_array_const ("exchange_candidates",
     302              :                                         &exchange_candidates),
     303              :           &exchange_candidates_missing),
     304            6 :         GNUNET_JSON_spec_end ()
     305              :       };
     306              : 
     307            6 :       if (GNUNET_OK !=
     308            6 :           GNUNET_JSON_parse (json,
     309              :                              spec,
     310              :                              NULL, NULL))
     311              :       {
     312            0 :         wtgr.hr.http_status = 0;
     313            0 :         wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     314            0 :         break;
     315              :       }
     316              :       {
     317            6 :         if (merchant_missing != merchant_pub_missing)
     318              :         {
     319            0 :           wtgr.hr.http_status = 0;
     320            0 :           wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     321            0 :           break;
     322              :         }
     323            6 :         if (! merchant_missing)
     324              :         {
     325            6 :           if (GNUNET_OK !=
     326            6 :               parse_merchant_metadata (merchant_object,
     327              :                                        &merchant))
     328              :           {
     329            0 :             wtgr.hr.http_status = 0;
     330            0 :             wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     331            0 :             break;
     332              :           }
     333            6 :           wtgr.details.ok.merchant = &merchant;
     334            6 :           wtgr.details.ok.merchant_pub = &merchant_pub;
     335              :         }
     336              :       }
     337            6 :       if (! exchange_candidates_missing)
     338              :       {
     339            6 :         if (GNUNET_OK !=
     340            6 :             parse_exchange_candidates (exchange_candidates,
     341              :                                        &num_candidates,
     342              :                                        &candidates))
     343              :         {
     344            0 :           wtgr.hr.http_status = 0;
     345            0 :           wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     346            0 :           break;
     347              :         }
     348            6 :         wtgr.details.ok.exchange_candidates_provided = true;
     349            6 :         wtgr.details.ok.num_exchange_candidates = num_candidates;
     350            6 :         wtgr.details.ok.exchange_candidates = candidates;
     351              :       }
     352            6 :       break;
     353              :     }
     354            0 :   case MHD_HTTP_UNAUTHORIZED:
     355            0 :     wtgr.hr.ec = TALER_JSON_get_error_code (json);
     356            0 :     wtgr.hr.hint = TALER_JSON_get_error_hint (json);
     357            0 :     break;
     358            0 :   case MHD_HTTP_NOT_FOUND:
     359            0 :     wtgr.hr.ec = TALER_JSON_get_error_code (json);
     360            0 :     wtgr.hr.hint = TALER_JSON_get_error_hint (json);
     361            0 :     break;
     362            0 :   default:
     363            0 :     wtgr.hr.ec = TALER_JSON_get_error_code (json);
     364            0 :     wtgr.hr.hint = TALER_JSON_get_error_hint (json);
     365            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     366              :                 "Unexpected response code %u/%d\n",
     367              :                 (unsigned int) response_code,
     368              :                 (int) wtgr.hr.ec);
     369            0 :     break;
     370              :   }
     371            6 :   gth->cb (gth->cb_cls,
     372              :            &wtgr);
     373            6 :   free_exchange_candidates (num_candidates,
     374              :                             candidates);
     375            6 :   free_merchant_metadata (&merchant);
     376            6 :   TALER_MERCHANT_get_templates_cancel (gth);
     377            6 : }
     378              : 
     379              : 
     380              : struct TALER_MERCHANT_GetTemplatesHandle *
     381            6 : TALER_MERCHANT_get_templates_create (
     382              :   struct GNUNET_CURL_Context *ctx,
     383              :   const char *url,
     384              :   const char *template_id)
     385              : {
     386              :   struct TALER_MERCHANT_GetTemplatesHandle *gth;
     387              : 
     388            6 :   gth = GNUNET_new (struct TALER_MERCHANT_GetTemplatesHandle);
     389            6 :   gth->ctx = ctx;
     390            6 :   gth->base_url = GNUNET_strdup (url);
     391            6 :   gth->template_id = GNUNET_strdup (template_id);
     392            6 :   return gth;
     393              : }
     394              : 
     395              : 
     396              : enum TALER_ErrorCode
     397            6 : TALER_MERCHANT_get_templates_start (
     398              :   struct TALER_MERCHANT_GetTemplatesHandle *gth,
     399              :   TALER_MERCHANT_GetTemplatesCallback cb,
     400              :   TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE *cb_cls)
     401              : {
     402              :   CURL *eh;
     403              : 
     404            6 :   gth->cb = cb;
     405            6 :   gth->cb_cls = cb_cls;
     406              :   {
     407              :     char *path;
     408              : 
     409            6 :     GNUNET_asprintf (&path,
     410              :                      "templates/%s",
     411              :                      gth->template_id);
     412            6 :     gth->url = TALER_url_join (gth->base_url,
     413              :                                path,
     414              :                                NULL);
     415            6 :     GNUNET_free (path);
     416              :   }
     417            6 :   if (NULL == gth->url)
     418            0 :     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
     419            6 :   eh = TALER_MERCHANT_curl_easy_get_ (gth->url);
     420            6 :   if (NULL == eh)
     421            0 :     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
     422            6 :   gth->job = GNUNET_CURL_job_add (gth->ctx,
     423              :                                   eh,
     424              :                                   &handle_get_template_finished,
     425              :                                   gth);
     426            6 :   if (NULL == gth->job)
     427            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     428            6 :   return TALER_EC_NONE;
     429              : }
     430              : 
     431              : 
     432              : void
     433            6 : TALER_MERCHANT_get_templates_cancel (
     434              :   struct TALER_MERCHANT_GetTemplatesHandle *gth)
     435              : {
     436            6 :   if (NULL != gth->job)
     437              :   {
     438            0 :     GNUNET_CURL_job_cancel (gth->job);
     439            0 :     gth->job = NULL;
     440              :   }
     441            6 :   GNUNET_free (gth->url);
     442            6 :   GNUNET_free (gth->base_url);
     443            6 :   GNUNET_free (gth->template_id);
     444            6 :   GNUNET_free (gth);
     445            6 : }
     446              : 
     447              : 
     448              : /* end of merchant_api_get-templates-TEMPLATE_ID-new.c */
        

Generated by: LCOV version 2.0-1