LCOV - code coverage report
Current view: top level - lib - merchant_api_common.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 167 236 70.8 %
Date: 2025-06-23 16:22:09 Functions: 6 6 100.0 %

          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          19 : TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
     113             :                               struct TALER_MERCHANT_PayUriData *parse_data)
     114             : {
     115          19 :   char *cp = GNUNET_strdup (pay_uri);
     116             :   struct GNUNET_Uri u;
     117             : 
     118          19 :   if (0 !=
     119          19 :       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          19 :   if ((0 != strcasecmp ("taler",
     127          19 :                         u.scheme)) &&
     128          15 :       (0 != strcasecmp ("taler+http",
     129          15 :                         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          18 :   parse_data->use_http = (0 == strcasecmp ("taler+http",
     139          18 :                                            u.scheme));
     140          18 :   if (0 != strcasecmp ("pay",
     141          18 :                        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          17 :     char *session_id = strrchr (u.path,
     152             :                                 '/');
     153          17 :     struct TALER_ClaimTokenP *claim_token = NULL;
     154             : 
     155          17 :     if (NULL == session_id)
     156             :     {
     157           0 :       GNUNET_break_op (0);
     158           0 :       GNUNET_free (cp);
     159           0 :       return GNUNET_SYSERR;
     160             :     }
     161          17 :     *session_id = '\0';
     162          17 :     ++session_id;
     163             : 
     164          17 :     order_id = strrchr (u.path,
     165             :                         '/');
     166          17 :     if (NULL == order_id)
     167             :     {
     168           1 :       GNUNET_break_op (0);
     169           1 :       GNUNET_free (cp);
     170           1 :       return GNUNET_SYSERR;
     171             :     }
     172          16 :     *order_id = '\0';
     173          16 :     ++order_id;
     174          16 :     mpp = strchr (u.path,
     175             :                   '/');
     176          16 :     if (NULL != mpp)
     177             :     {
     178           1 :       *mpp = '\0';
     179           1 :       ++mpp;
     180             :     }
     181             : 
     182             :     {
     183          16 :       char *ct_str = u.query;
     184             :       char *ct_data;
     185             : 
     186          16 :       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          16 :       = (NULL == mpp)
     217             :         ? NULL
     218          16 :         : GNUNET_strdup (mpp);
     219          16 :     parse_data->merchant_host = GNUNET_strdup (u.path);
     220          16 :     parse_data->order_id = GNUNET_strdup (order_id);
     221             :     parse_data->session_id
     222          32 :       = (0 < strlen (session_id))
     223          10 :         ? GNUNET_strdup (session_id)
     224          16 :         : NULL;
     225          16 :     parse_data->claim_token = claim_token;
     226             :     parse_data->ssid
     227          32 :       = (NULL == u.fragment)
     228             :         ? NULL
     229          16 :         : GNUNET_strdup (u.fragment);
     230             :   }
     231          16 :   GNUNET_free (cp);
     232          16 :   return GNUNET_OK;
     233             : }
     234             : 
     235             : 
     236             : void
     237          16 : TALER_MERCHANT_parse_pay_uri_free (
     238             :   struct TALER_MERCHANT_PayUriData *parse_data)
     239             : {
     240          16 :   GNUNET_free (parse_data->merchant_host);
     241          16 :   GNUNET_free (parse_data->merchant_prefix_path);
     242          16 :   GNUNET_free (parse_data->order_id);
     243          16 :   GNUNET_free (parse_data->session_id);
     244          16 :   GNUNET_free (parse_data->claim_token);
     245          16 :   GNUNET_free (parse_data->ssid);
     246          16 : }
     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          64 : 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          64 :   struct TALER_MERCHANT_PostOrdersReply por = {
     363          64 :     .hr.http_status = (unsigned int) response_code,
     364             :     .hr.reply = json
     365             :   };
     366             :   struct TALER_ClaimTokenP token;
     367             : 
     368          64 :   switch (response_code)
     369             :   {
     370           0 :   case 0:
     371           0 :     por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     372           0 :     break;
     373          44 :   case MHD_HTTP_OK:
     374             :     {
     375             :       bool no_token;
     376             :       struct GNUNET_JSON_Specification spec[] = {
     377          44 :         GNUNET_JSON_spec_string ("order_id",
     378             :                                  &por.details.ok.order_id),
     379          44 :         GNUNET_JSON_spec_mark_optional (
     380             :           GNUNET_JSON_spec_fixed_auto ("token",
     381             :                                        &token),
     382             :           &no_token),
     383          44 :         GNUNET_JSON_spec_end ()
     384             :       };
     385             : 
     386          44 :       if (GNUNET_OK !=
     387          44 :           GNUNET_JSON_parse (json,
     388             :                              spec,
     389             :                              NULL, NULL))
     390             :       {
     391           0 :         GNUNET_break_op (0);
     392           0 :         por.hr.http_status = 0;
     393           0 :         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     394             :       }
     395             :       else
     396             :       {
     397          44 :         if (! no_token)
     398          40 :           por.details.ok.token = &token;
     399             :       }
     400             :     }
     401          44 :     break;
     402           0 :   case MHD_HTTP_BAD_REQUEST:
     403           0 :     json_dumpf (json,
     404             :                 stderr,
     405             :                 JSON_INDENT (2));
     406           0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     407           0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     408             :     /* This should never happen, either us or
     409             :        the merchant is buggy (or API version conflict);
     410             :        just pass JSON reply to the application */
     411           0 :     break;
     412           0 :   case MHD_HTTP_UNAUTHORIZED:
     413           0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     414           0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     415             :     /* Nothing really to verify, merchant says we need to authenticate. */
     416           0 :     break;
     417           0 :   case MHD_HTTP_FORBIDDEN:
     418             :     /* Nothing really to verify, merchant says one
     419             :        of the signatures is invalid; as we checked them,
     420             :        this should never happen, we should pass the JSON
     421             :        reply to the application */
     422           0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     423           0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     424           0 :     break;
     425           6 :   case MHD_HTTP_NOT_FOUND:
     426             :     /* Nothing really to verify, this should never
     427             :        happen, we should pass the JSON reply to the application */
     428           6 :     por.hr.ec = TALER_JSON_get_error_code (json);
     429           6 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     430           6 :     break;
     431          12 :   case MHD_HTTP_CONFLICT:
     432          12 :     por.hr.ec = TALER_JSON_get_error_code (json);
     433          12 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     434          12 :     break;
     435           2 :   case MHD_HTTP_GONE:
     436             :     /* The quantity of some product requested was not available. */
     437             :     {
     438             : 
     439             :       struct GNUNET_JSON_Specification spec[] = {
     440           2 :         GNUNET_JSON_spec_string (
     441             :           "product_id",
     442             :           &por.details.gone.product_id),
     443           2 :         GNUNET_JSON_spec_uint64 (
     444             :           "requested_quantity",
     445             :           &por.details.gone.requested_quantity),
     446           2 :         GNUNET_JSON_spec_uint64 (
     447             :           "available_quantity",
     448             :           &por.details.gone.available_quantity),
     449           2 :         GNUNET_JSON_spec_mark_optional (
     450             :           GNUNET_JSON_spec_timestamp (
     451             :             "restock_expected",
     452             :             &por.details.gone.restock_expected),
     453             :           NULL),
     454           2 :         GNUNET_JSON_spec_end ()
     455             :       };
     456             : 
     457           2 :       if (GNUNET_OK !=
     458           2 :           GNUNET_JSON_parse (json,
     459             :                              spec,
     460             :                              NULL, NULL))
     461             :       {
     462           0 :         GNUNET_break_op (0);
     463           0 :         por.hr.http_status = 0;
     464           0 :         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     465             :       }
     466           2 :       break;
     467             :     }
     468           0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     469             :     /* Order total too high for legal reasons */
     470           0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     471           0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     472           0 :     break;
     473           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     474             :     /* Server had an internal issue; we should retry,
     475             :        but this API leaves this to the application */
     476           0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     477           0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     478           0 :     break;
     479           0 :   default:
     480             :     /* unexpected response code */
     481           0 :     por.hr.ec = TALER_JSON_get_error_code (json);
     482           0 :     por.hr.hint = TALER_JSON_get_error_hint (json);
     483           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     484             :                 "Unexpected response code %u/%d\n",
     485             :                 (unsigned int) response_code,
     486             :                 (int) por.hr.ec);
     487           0 :     GNUNET_break_op (0);
     488           0 :     break;
     489             :   } // end of switch
     490          64 :   cb (cb_cls,
     491             :       &por);
     492          64 : }

Generated by: LCOV version 1.16