LCOV - code coverage report
Current view: top level - lib - merchant_api_common.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 71.2 % 240 171
Test Date: 2025-10-31 14:20:14 Functions: 100.0 % 6 6

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2020-2023 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 merchant_api_common.c
      19              :  * @brief Implementation of common logic for libtalermerchant
      20              :  * @author Christian Grothoff
      21              :  * @author Priscilla Huang
      22              :  */
      23              : #include "platform.h"
      24              : #include "microhttpd.h"
      25              : #include <curl/curl.h>
      26              : #include "taler_merchant_service.h"
      27              : #include "merchant_api_common.h"
      28              : #include <gnunet/gnunet_uri_lib.h>
      29              : #include <taler/taler_json_lib.h>
      30              : 
      31              : 
      32              : void
      33           10 : TALER_MERCHANT_parse_error_details_ (const json_t *response,
      34              :                                      unsigned int http_status,
      35              :                                      struct TALER_MERCHANT_HttpResponse *hr)
      36              : {
      37              :   const json_t *jc;
      38              : 
      39           10 :   memset (hr, 0, sizeof (*hr));
      40           10 :   hr->reply = response;
      41           10 :   hr->http_status = http_status;
      42           10 :   if (NULL == response)
      43              :   {
      44            0 :     hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE;
      45            0 :     return;
      46              :   }
      47           10 :   hr->ec = TALER_JSON_get_error_code (response);
      48           10 :   hr->hint = TALER_JSON_get_error_hint (response);
      49              : 
      50              :   /* handle 'exchange_http_status' */
      51           10 :   jc = json_object_get (response,
      52              :                         "exchange_http_status");
      53              :   /* The caller already knows that the JSON represents an error,
      54              :      so we are dealing with a missing error code here.  */
      55           10 :   if (NULL == jc)
      56            4 :     return; /* no need to bother with exchange_code/hint if we had no status */
      57            6 :   if (! json_is_integer (jc))
      58              :   {
      59            0 :     GNUNET_break_op (0);
      60            0 :     return;
      61              :   }
      62            6 :   hr->exchange_http_status = (unsigned int) json_integer_value (jc);
      63              : 
      64              :   /* handle 'exchange_reply' */
      65            6 :   jc = json_object_get (response,
      66              :                         "exchange_reply");
      67            6 :   if (! json_is_object (jc))
      68              :   {
      69            0 :     GNUNET_break_op (0);
      70              :   }
      71              :   else
      72              :   {
      73            6 :     hr->exchange_reply = jc;
      74              :   }
      75              : 
      76              :   /* handle 'exchange_code' */
      77            6 :   jc = json_object_get (response,
      78              :                         "exchange_code");
      79              :   /* The caller already knows that the JSON represents an error,
      80              :      so we are dealing with a missing error code here.  */
      81            6 :   if (NULL == jc)
      82            0 :     return; /* no need to bother with exchange-hint if we had no code */
      83            6 :   if (! json_is_integer (jc))
      84              :   {
      85            0 :     GNUNET_break_op (0);
      86            0 :     hr->exchange_code = TALER_EC_INVALID;
      87              :   }
      88              :   else
      89              :   {
      90            6 :     hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc);
      91              :   }
      92              : 
      93              :   /* handle 'exchange-hint' */
      94            6 :   jc = json_object_get (response,
      95              :                         "exchange-hint");
      96              :   /* The caller already knows that the JSON represents an error,
      97              :      so we are dealing with a missing error code here.  */
      98            6 :   if (NULL == jc)
      99            6 :     return;
     100            0 :   if (! json_is_string (jc))
     101              :   {
     102            0 :     GNUNET_break_op (0);
     103              :   }
     104              :   else
     105              :   {
     106            0 :     hr->exchange_hint = json_string_value (jc);
     107              :   }
     108              : }
     109              : 
     110              : 
     111              : enum GNUNET_GenericReturnValue
     112           20 : TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
     113              :                               struct TALER_MERCHANT_PayUriData *parse_data)
     114              : {
     115           20 :   char *cp = GNUNET_strdup (pay_uri);
     116              :   struct GNUNET_Uri u;
     117              : 
     118           20 :   if (0 !=
     119           20 :       GNUNET_uri_parse (&u,
     120              :                         cp))
     121              :   {
     122            0 :     GNUNET_free (cp);
     123            0 :     GNUNET_break_op (0);
     124            0 :     return GNUNET_SYSERR;
     125              :   }
     126           20 :   if ((0 != strcasecmp ("taler",
     127           20 :                         u.scheme)) &&
     128           16 :       (0 != strcasecmp ("taler+http",
     129           16 :                         u.scheme)))
     130              :   {
     131            1 :     fprintf (stderr,
     132              :              "Bad schema %s\n",
     133              :              u.scheme);
     134            1 :     GNUNET_free (cp);
     135            1 :     GNUNET_break_op (0);
     136            1 :     return GNUNET_SYSERR;
     137              :   }
     138           19 :   parse_data->use_http = (0 == strcasecmp ("taler+http",
     139           19 :                                            u.scheme));
     140           19 :   if (0 != strcasecmp ("pay",
     141           19 :                        u.host))
     142              :   {
     143            1 :     GNUNET_break_op (0);
     144            1 :     GNUNET_free (cp);
     145            1 :     return GNUNET_SYSERR;
     146              :   }
     147              : 
     148              :   {
     149              :     char *order_id;
     150              :     char *mpp;
     151           18 :     char *session_id = strrchr (u.path,
     152              :                                 '/');
     153           18 :     struct TALER_ClaimTokenP *claim_token = NULL;
     154              : 
     155           18 :     if (NULL == session_id)
     156              :     {
     157            0 :       GNUNET_break_op (0);
     158            0 :       GNUNET_free (cp);
     159            0 :       return GNUNET_SYSERR;
     160              :     }
     161           18 :     *session_id = '\0';
     162           18 :     ++session_id;
     163              : 
     164           18 :     order_id = strrchr (u.path,
     165              :                         '/');
     166           18 :     if (NULL == order_id)
     167              :     {
     168            1 :       GNUNET_break_op (0);
     169            1 :       GNUNET_free (cp);
     170            1 :       return GNUNET_SYSERR;
     171              :     }
     172           17 :     *order_id = '\0';
     173           17 :     ++order_id;
     174           17 :     mpp = strchr (u.path,
     175              :                   '/');
     176           17 :     if (NULL != mpp)
     177              :     {
     178            1 :       *mpp = '\0';
     179            1 :       ++mpp;
     180              :     }
     181              : 
     182              :     {
     183           17 :       char *ct_str = u.query;
     184              :       char *ct_data;
     185              : 
     186           17 :       if (NULL != ct_str)
     187              :       {
     188            7 :         ct_data = strchr (u.query,
     189              :                           '=');
     190            7 :         if (NULL == ct_data)
     191              :         {
     192            0 :           GNUNET_break_op (0);
     193            0 :           GNUNET_free (cp);
     194            0 :           return GNUNET_SYSERR;
     195              :         }
     196            7 :         *ct_data = '\0';
     197            7 :         ++ct_data;
     198            7 :         claim_token = GNUNET_new (struct TALER_ClaimTokenP);
     199            7 :         if ( (0 != strcmp ("c",
     200           14 :                            u.query)) ||
     201              :              (GNUNET_OK !=
     202            7 :               GNUNET_STRINGS_string_to_data (ct_data,
     203              :                                              strlen (ct_data),
     204              :                                              claim_token,
     205              :                                              sizeof (*claim_token))) )
     206              :         {
     207            0 :           GNUNET_break_op (0);
     208            0 :           GNUNET_free (claim_token);
     209            0 :           GNUNET_free (cp);
     210            0 :           return GNUNET_SYSERR;
     211              :         }
     212              :       }
     213              :     }
     214              : 
     215              :     parse_data->merchant_prefix_path
     216           17 :       = (NULL == mpp)
     217              :         ? NULL
     218           17 :         : GNUNET_strdup (mpp);
     219           17 :     parse_data->merchant_host = GNUNET_strdup (u.path);
     220           17 :     parse_data->order_id = GNUNET_strdup (order_id);
     221              :     parse_data->session_id
     222           34 :       = (0 < strlen (session_id))
     223           10 :         ? GNUNET_strdup (session_id)
     224           17 :         : NULL;
     225           17 :     parse_data->claim_token = claim_token;
     226              :     parse_data->ssid
     227           34 :       = (NULL == u.fragment)
     228              :         ? NULL
     229           17 :         : GNUNET_strdup (u.fragment);
     230              :   }
     231           17 :   GNUNET_free (cp);
     232           17 :   return GNUNET_OK;
     233              : }
     234              : 
     235              : 
     236              : void
     237           17 : TALER_MERCHANT_parse_pay_uri_free (
     238              :   struct TALER_MERCHANT_PayUriData *parse_data)
     239              : {
     240           17 :   GNUNET_free (parse_data->merchant_host);
     241           17 :   GNUNET_free (parse_data->merchant_prefix_path);
     242           17 :   GNUNET_free (parse_data->order_id);
     243           17 :   GNUNET_free (parse_data->session_id);
     244           17 :   GNUNET_free (parse_data->claim_token);
     245           17 :   GNUNET_free (parse_data->ssid);
     246           17 : }
     247              : 
     248              : 
     249              : enum GNUNET_GenericReturnValue
     250           11 : TALER_MERCHANT_parse_refund_uri (
     251              :   const char *refund_uri,
     252              :   struct TALER_MERCHANT_RefundUriData *parse_data)
     253              : {
     254           11 :   char *cp = GNUNET_strdup (refund_uri);
     255              :   struct GNUNET_Uri u;
     256              : 
     257           11 :   if (0 !=
     258           11 :       GNUNET_uri_parse (&u,
     259              :                         cp))
     260              :   {
     261            0 :     GNUNET_free (cp);
     262            0 :     GNUNET_break_op (0);
     263            0 :     return GNUNET_SYSERR;
     264              :   }
     265           11 :   if ((0 != strcasecmp ("taler",
     266           11 :                         u.scheme)) &&
     267            8 :       (0 != strcasecmp ("taler+http",
     268            8 :                         u.scheme)))
     269              :   {
     270            1 :     GNUNET_free (cp);
     271            1 :     GNUNET_break_op (0);
     272            1 :     return GNUNET_SYSERR;
     273              :   }
     274           10 :   parse_data->use_http = (0 == strcasecmp ("taler+http",
     275           10 :                                            u.scheme));
     276              : 
     277           10 :   if (0 != strcasecmp ("refund",
     278           10 :                        u.host))
     279              :   {
     280            1 :     GNUNET_break_op (0);
     281            1 :     GNUNET_free (cp);
     282            1 :     return GNUNET_SYSERR;
     283              :   }
     284              : 
     285              :   {
     286              :     char *order_id;
     287            9 :     char *last_seg = strrchr (u.path,
     288              :                               '/');
     289              : 
     290            9 :     if (NULL == last_seg)
     291              :     {
     292            0 :       GNUNET_break_op (0);
     293            0 :       GNUNET_free (cp);
     294            0 :       return GNUNET_SYSERR;
     295              :     }
     296            9 :     *last_seg = '\0';
     297            9 :     ++last_seg;
     298              : 
     299            9 :     order_id = strrchr (u.path,
     300              :                         '/');
     301            9 :     if (NULL == order_id)
     302              :     {
     303            1 :       GNUNET_break_op (0);
     304            1 :       GNUNET_free (cp);
     305            1 :       return GNUNET_SYSERR;
     306              :     }
     307            8 :     *order_id = '\0';
     308            8 :     ++order_id;
     309            8 :     if (0 != strlen (last_seg))
     310              :     {
     311            0 :       GNUNET_break_op (0);
     312            0 :       GNUNET_free (cp);
     313            0 :       return GNUNET_SYSERR;
     314              :     }
     315              : 
     316              :     {
     317              :       char *mpp;
     318              : 
     319            8 :       mpp = strchr (u.path,
     320              :                     '/');
     321            8 :       if (NULL != mpp)
     322              :       {
     323            1 :         *mpp = '\0';
     324            1 :         ++mpp;
     325              :       }
     326              : 
     327              :       parse_data->merchant_prefix_path
     328            8 :         = (NULL == mpp)
     329              :           ? NULL
     330            8 :           : GNUNET_strdup (mpp);
     331              :     }
     332            8 :     parse_data->merchant_host = GNUNET_strdup (u.path);
     333            8 :     parse_data->order_id = GNUNET_strdup (order_id);
     334              :     parse_data->ssid
     335           16 :       = (NULL == u.fragment)
     336              :         ? NULL
     337            8 :         : GNUNET_strdup (u.fragment);
     338              :   }
     339            8 :   GNUNET_free (cp);
     340            8 :   return GNUNET_OK;
     341              : }
     342              : 
     343              : 
     344              : void
     345            8 : TALER_MERCHANT_parse_refund_uri_free (
     346              :   struct TALER_MERCHANT_RefundUriData *parse_data)
     347              : {
     348            8 :   GNUNET_free (parse_data->merchant_host);
     349            8 :   GNUNET_free (parse_data->merchant_prefix_path);
     350            8 :   GNUNET_free (parse_data->order_id);
     351            8 :   GNUNET_free (parse_data->ssid);
     352            8 : }
     353              : 
     354              : 
     355              : void
     356           65 : TALER_MERCHANT_handle_order_creation_response_ (
     357              :   TALER_MERCHANT_PostOrdersCallback cb,
     358              :   void *cb_cls,
     359              :   long response_code,
     360              :   const json_t *json)
     361              : {
     362           65 :   struct TALER_MERCHANT_PostOrdersReply por = {
     363           65 :     .hr.http_status = (unsigned int) response_code,
     364              :     .hr.reply = json
     365              :   };
     366              :   struct TALER_ClaimTokenP token;
     367              : 
     368           65 :   switch (response_code)
     369              :   {
     370            0 :   case 0:
     371            0 :     por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     372            0 :     break;
     373           45 :   case MHD_HTTP_OK:
     374              :     {
     375              :       bool no_token;
     376              :       bool no_pay_deadline;
     377              :       struct GNUNET_JSON_Specification spec[] = {
     378           45 :         GNUNET_JSON_spec_string ("order_id",
     379              :                                  &por.details.ok.order_id),
     380           45 :         GNUNET_JSON_spec_mark_optional (
     381              :           GNUNET_JSON_spec_fixed_auto ("token",
     382              :                                        &token),
     383              :           &no_token),
     384           45 :         GNUNET_JSON_spec_mark_optional (
     385              :           /* optional for pre-v21 compatibility */
     386              :           GNUNET_JSON_spec_timestamp ("pay_deadline",
     387              :                                       &por.details.ok.pay_deadline),
     388              :           &no_pay_deadline),
     389           45 :         GNUNET_JSON_spec_end ()
     390              :       };
     391              : 
     392           45 :       if (GNUNET_OK !=
     393           45 :           GNUNET_JSON_parse (json,
     394              :                              spec,
     395              :                              NULL, NULL))
     396              :       {
     397            0 :         GNUNET_break_op (0);
     398            0 :         por.hr.http_status = 0;
     399            0 :         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     400              :       }
     401              :       else
     402              :       {
     403           45 :         if (! no_token)
     404           41 :           por.details.ok.token = &token;
     405           45 :         if (no_pay_deadline)
     406              :         {
     407           43 :           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     408              :                       "Talking to outdated merchant backend, payment deadline unavailable.\n");
     409           43 :           por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
     410              :         }
     411              :       }
     412              :     }
     413           45 :     break;
     414            0 :   case MHD_HTTP_BAD_REQUEST:
     415            0 :     json_dumpf (json,
     416              :                 stderr,
     417              :                 JSON_INDENT (2));
     418            0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     419            0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     420              :     /* This should never happen, either us or
     421              :        the merchant is buggy (or API version conflict);
     422              :        just pass JSON reply to the application */
     423            0 :     break;
     424            0 :   case MHD_HTTP_UNAUTHORIZED:
     425            0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     426            0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     427              :     /* Nothing really to verify, merchant says we need to authenticate. */
     428            0 :     break;
     429            0 :   case MHD_HTTP_FORBIDDEN:
     430              :     /* Nothing really to verify, merchant says one
     431              :        of the signatures is invalid; as we checked them,
     432              :        this should never happen, we should pass the JSON
     433              :        reply to the application */
     434            0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     435            0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     436            0 :     break;
     437            6 :   case MHD_HTTP_NOT_FOUND:
     438              :     /* Nothing really to verify, this should never
     439              :        happen, we should pass the JSON reply to the application */
     440            6 :     por.hr.ec = TALER_JSON_get_error_code (json);
     441            6 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     442            6 :     break;
     443           12 :   case MHD_HTTP_CONFLICT:
     444           12 :     por.hr.ec = TALER_JSON_get_error_code (json);
     445           12 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     446           12 :     break;
     447            2 :   case MHD_HTTP_GONE:
     448              :     /* The quantity of some product requested was not available. */
     449              :     {
     450              : 
     451              :       struct GNUNET_JSON_Specification spec[] = {
     452            2 :         GNUNET_JSON_spec_string (
     453              :           "product_id",
     454              :           &por.details.gone.product_id),
     455            2 :         GNUNET_JSON_spec_uint64 (
     456              :           "requested_quantity",
     457              :           &por.details.gone.requested_quantity),
     458            2 :         GNUNET_JSON_spec_uint64 (
     459              :           "available_quantity",
     460              :           &por.details.gone.available_quantity),
     461            2 :         GNUNET_JSON_spec_mark_optional (
     462              :           GNUNET_JSON_spec_timestamp (
     463              :             "restock_expected",
     464              :             &por.details.gone.restock_expected),
     465              :           NULL),
     466            2 :         GNUNET_JSON_spec_end ()
     467              :       };
     468              : 
     469            2 :       if (GNUNET_OK !=
     470            2 :           GNUNET_JSON_parse (json,
     471              :                              spec,
     472              :                              NULL, NULL))
     473              :       {
     474            0 :         GNUNET_break_op (0);
     475            0 :         por.hr.http_status = 0;
     476            0 :         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     477              :       }
     478            2 :       break;
     479              :     }
     480            0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     481              :     /* Order total too high for legal reasons */
     482            0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     483            0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     484            0 :     break;
     485            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     486              :     /* Server had an internal issue; we should retry,
     487              :        but this API leaves this to the application */
     488            0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     489            0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     490            0 :     break;
     491            0 :   default:
     492              :     /* unexpected response code */
     493            0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     494            0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     495            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     496              :                 "Unexpected response code %u/%d\n",
     497              :                 (unsigned int) response_code,
     498              :                 (int) por.hr.ec);
     499            0 :     GNUNET_break_op (0);
     500            0 :     break;
     501              :   } // end of switch
     502           65 :   cb (cb_cls,
     503              :       &por);
     504           65 : }
        

Generated by: LCOV version 2.0-1