merchant_api_get-private-units.c (8781B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025-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 merchant_api_get-private-units.c 19 * @brief Implementation of the GET /private/units request 20 * @author Christian Grothoff 21 */ 22 #include "taler/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/taler-merchant/get-private-units.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of units returned in a single response. 35 */ 36 #define MAX_UNITS 1024 37 38 39 /** 40 * Handle for a GET /private/units operation. 41 */ 42 struct TALER_MERCHANT_GetPrivateUnitsHandle 43 { 44 /** 45 * Base URL of the merchant backend. 46 */ 47 char *base_url; 48 49 /** 50 * The full URL for this request. 51 */ 52 char *url; 53 54 /** 55 * Handle for the request. 56 */ 57 struct GNUNET_CURL_Job *job; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_MERCHANT_GetPrivateUnitsCallback cb; 63 64 /** 65 * Closure for @a cb. 66 */ 67 TALER_MERCHANT_GET_PRIVATE_UNITS_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 }; 74 75 76 /** 77 * Parse an individual unit entry from @a value. 78 * 79 * @param value JSON object describing the unit 80 * @param[out] ue set to the parsed values 81 * @return #GNUNET_OK on success 82 */ 83 static enum GNUNET_GenericReturnValue 84 parse_unit_entry (const json_t *value, 85 struct TALER_MERCHANT_UnitEntry *ue) 86 { 87 struct GNUNET_JSON_Specification spec[] = { 88 GNUNET_JSON_spec_string ("unit", 89 &ue->unit), 90 GNUNET_JSON_spec_string ("unit_name_long", 91 &ue->unit_name_long), 92 GNUNET_JSON_spec_string ("unit_name_short", 93 &ue->unit_name_short), 94 GNUNET_JSON_spec_mark_optional ( 95 GNUNET_JSON_spec_object_const ("unit_name_long_i18n", 96 &ue->unit_name_long_i18n), 97 NULL), 98 GNUNET_JSON_spec_mark_optional ( 99 GNUNET_JSON_spec_object_const ("unit_name_short_i18n", 100 &ue->unit_name_short_i18n), 101 NULL), 102 GNUNET_JSON_spec_bool ("unit_allow_fraction", 103 &ue->unit_allow_fraction), 104 GNUNET_JSON_spec_uint32 ("unit_precision_level", 105 &ue->unit_precision_level), 106 GNUNET_JSON_spec_bool ("unit_active", 107 &ue->unit_active), 108 GNUNET_JSON_spec_bool ("unit_builtin", 109 &ue->unit_builtin), 110 GNUNET_JSON_spec_uint64 ("unit_serial", 111 &ue->unit_serial), 112 GNUNET_JSON_spec_end () 113 }; 114 115 memset (ue, 116 0, 117 sizeof (*ue)); 118 if (GNUNET_OK != 119 GNUNET_JSON_parse (value, 120 spec, 121 NULL, 122 NULL)) 123 { 124 GNUNET_break_op (0); 125 return GNUNET_SYSERR; 126 } 127 return GNUNET_OK; 128 } 129 130 131 /** 132 * Parse the list of units from @a units and call the callback. 133 * 134 * @param json complete response JSON 135 * @param units array of units 136 * @param gpuh ongoing operation handle 137 * @return #GNUNET_OK on success 138 */ 139 static enum GNUNET_GenericReturnValue 140 parse_units (const json_t *json, 141 const json_t *units, 142 struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh) 143 { 144 size_t len = json_array_size (units); 145 146 if (len > MAX_UNITS) 147 { 148 GNUNET_break_op (0); 149 return GNUNET_SYSERR; 150 } 151 152 { 153 struct TALER_MERCHANT_UnitEntry entries[GNUNET_NZL (len)]; 154 size_t idx; 155 json_t *value; 156 157 json_array_foreach (units, idx, value) { 158 if (GNUNET_OK != 159 parse_unit_entry (value, 160 &entries[idx])) 161 { 162 GNUNET_break_op (0); 163 return GNUNET_SYSERR; 164 } 165 } 166 { 167 struct TALER_MERCHANT_GetPrivateUnitsResponse ugr = { 168 .hr.http_status = MHD_HTTP_OK, 169 .hr.reply = json, 170 .details.ok.units = entries, 171 .details.ok.units_length = (unsigned int) len 172 }; 173 174 gpuh->cb (gpuh->cb_cls, 175 &ugr); 176 } 177 } 178 return GNUNET_OK; 179 } 180 181 182 /** 183 * Function called when we're done processing the 184 * HTTP GET /private/units request. 185 * 186 * @param cls the `struct TALER_MERCHANT_GetPrivateUnitsHandle` 187 * @param response_code HTTP response code, 0 on error 188 * @param response response body, NULL if not in JSON 189 */ 190 static void 191 handle_get_units_finished (void *cls, 192 long response_code, 193 const void *response) 194 { 195 struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh = cls; 196 const json_t *json = response; 197 struct TALER_MERCHANT_GetPrivateUnitsResponse ugr = { 198 .hr.http_status = (unsigned int) response_code, 199 .hr.reply = json 200 }; 201 202 gpuh->job = NULL; 203 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 204 "Got /private/units response with status code %u\n", 205 (unsigned int) response_code); 206 switch (response_code) 207 { 208 case MHD_HTTP_OK: 209 { 210 const json_t *units; 211 struct GNUNET_JSON_Specification spec[] = { 212 GNUNET_JSON_spec_array_const ("units", 213 &units), 214 GNUNET_JSON_spec_end () 215 }; 216 217 if (GNUNET_OK != 218 GNUNET_JSON_parse (json, 219 spec, 220 NULL, NULL)) 221 { 222 GNUNET_break_op (0); 223 ugr.hr.http_status = 0; 224 ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 225 break; 226 } 227 if (GNUNET_OK == 228 parse_units (json, 229 units, 230 gpuh)) 231 { 232 TALER_MERCHANT_get_private_units_cancel (gpuh); 233 return; 234 } 235 ugr.hr.http_status = 0; 236 ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 237 break; 238 } 239 case MHD_HTTP_UNAUTHORIZED: 240 case MHD_HTTP_FORBIDDEN: 241 case MHD_HTTP_NOT_FOUND: 242 case MHD_HTTP_CONFLICT: 243 ugr.hr.ec = TALER_JSON_get_error_code (json); 244 ugr.hr.hint = TALER_JSON_get_error_hint (json); 245 break; 246 case 0: 247 ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 248 break; 249 default: 250 ugr.hr.ec = TALER_JSON_get_error_code (json); 251 ugr.hr.hint = TALER_JSON_get_error_hint (json); 252 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 253 "Unexpected response code %u/%d for GET /private/units\n", 254 (unsigned int) response_code, 255 (int) ugr.hr.ec); 256 break; 257 } 258 gpuh->cb (gpuh->cb_cls, 259 &ugr); 260 TALER_MERCHANT_get_private_units_cancel (gpuh); 261 } 262 263 264 struct TALER_MERCHANT_GetPrivateUnitsHandle * 265 TALER_MERCHANT_get_private_units_create ( 266 struct GNUNET_CURL_Context *ctx, 267 const char *url) 268 { 269 struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh; 270 271 gpuh = GNUNET_new (struct TALER_MERCHANT_GetPrivateUnitsHandle); 272 gpuh->ctx = ctx; 273 gpuh->base_url = GNUNET_strdup (url); 274 return gpuh; 275 } 276 277 278 enum TALER_ErrorCode 279 TALER_MERCHANT_get_private_units_start ( 280 struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh, 281 TALER_MERCHANT_GetPrivateUnitsCallback cb, 282 TALER_MERCHANT_GET_PRIVATE_UNITS_RESULT_CLOSURE *cb_cls) 283 { 284 CURL *eh; 285 286 gpuh->cb = cb; 287 gpuh->cb_cls = cb_cls; 288 gpuh->url = TALER_url_join (gpuh->base_url, 289 "private/units", 290 NULL); 291 if (NULL == gpuh->url) 292 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 293 eh = TALER_MERCHANT_curl_easy_get_ (gpuh->url); 294 if (NULL == eh) 295 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 296 gpuh->job = GNUNET_CURL_job_add (gpuh->ctx, 297 eh, 298 &handle_get_units_finished, 299 gpuh); 300 if (NULL == gpuh->job) 301 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 302 return TALER_EC_NONE; 303 } 304 305 306 void 307 TALER_MERCHANT_get_private_units_cancel ( 308 struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh) 309 { 310 if (NULL != gpuh->job) 311 { 312 GNUNET_CURL_job_cancel (gpuh->job); 313 gpuh->job = NULL; 314 } 315 GNUNET_free (gpuh->url); 316 GNUNET_free (gpuh->base_url); 317 GNUNET_free (gpuh); 318 } 319 320 321 /* end of merchant_api_get-private-units.c */