merchant_api_get-private-products.c (12539B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-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-products.c 19 * @brief Implementation of the GET /private/products 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-products.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of products we return. 35 */ 36 #define MAX_PRODUCTS 1024 37 38 39 /** 40 * Handle for a GET /private/products operation. 41 */ 42 struct TALER_MERCHANT_GetPrivateProductsHandle 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_GetPrivateProductsCallback cb; 63 64 /** 65 * Closure for @a cb. 66 */ 67 TALER_MERCHANT_GET_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Starting row for pagination. 76 */ 77 uint64_t offset; 78 79 /** 80 * Limit on number of results. 81 */ 82 int64_t limit; 83 84 /** 85 * Category name filter, or NULL. 86 */ 87 char *category_filter; 88 89 /** 90 * Product name filter, or NULL. 91 */ 92 char *name_filter; 93 94 /** 95 * Product description filter, or NULL. 96 */ 97 char *description_filter; 98 99 /** 100 * Product group serial filter. 101 */ 102 uint64_t product_group_serial; 103 104 /** 105 * True if offset was explicitly set. 106 */ 107 bool have_offset; 108 109 /** 110 * True if product_group_serial was explicitly set. 111 */ 112 bool have_product_group_serial; 113 }; 114 115 116 /** 117 * Parse product information from @a ia. 118 * 119 * @param ia JSON array (or NULL!) with product data 120 * @param[in] pgr partially filled response 121 * @param gpph operation handle 122 * @return #GNUNET_OK on success 123 */ 124 static enum GNUNET_GenericReturnValue 125 parse_products (const json_t *ia, 126 struct TALER_MERCHANT_GetPrivateProductsResponse *pgr, 127 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph) 128 { 129 unsigned int ies_len = (unsigned int) json_array_size (ia); 130 131 if ( (json_array_size (ia) != (size_t) ies_len) || 132 (ies_len > MAX_PRODUCTS) ) 133 { 134 GNUNET_break (0); 135 return GNUNET_SYSERR; 136 } 137 { 138 struct TALER_MERCHANT_GetPrivateProductsInventoryEntry ies[ 139 GNUNET_NZL (ies_len)]; 140 size_t index; 141 json_t *value; 142 143 json_array_foreach (ia, index, value) { 144 struct TALER_MERCHANT_GetPrivateProductsInventoryEntry *ie = 145 &ies[index]; 146 struct GNUNET_JSON_Specification spec[] = { 147 GNUNET_JSON_spec_string ("product_id", 148 &ie->product_id), 149 GNUNET_JSON_spec_uint64 ("product_serial", 150 &ie->product_serial), 151 GNUNET_JSON_spec_end () 152 }; 153 154 if (GNUNET_OK != 155 GNUNET_JSON_parse (value, 156 spec, 157 NULL, NULL)) 158 { 159 GNUNET_break_op (0); 160 return GNUNET_SYSERR; 161 } 162 } 163 pgr->details.ok.products_length = ies_len; 164 pgr->details.ok.products = ies; 165 gpph->cb (gpph->cb_cls, 166 pgr); 167 gpph->cb = NULL; 168 } 169 return GNUNET_OK; 170 } 171 172 173 /** 174 * Function called when we're done processing the 175 * HTTP GET /private/products request. 176 * 177 * @param cls the `struct TALER_MERCHANT_GetPrivateProductsHandle` 178 * @param response_code HTTP response code, 0 on error 179 * @param response response body, NULL if not in JSON 180 */ 181 static void 182 handle_get_products_finished (void *cls, 183 long response_code, 184 const void *response) 185 { 186 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph = cls; 187 const json_t *json = response; 188 struct TALER_MERCHANT_GetPrivateProductsResponse pgr = { 189 .hr.http_status = (unsigned int) response_code, 190 .hr.reply = json 191 }; 192 193 gpph->job = NULL; 194 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 195 "Got /private/products response with status code %u\n", 196 (unsigned int) response_code); 197 switch (response_code) 198 { 199 case MHD_HTTP_OK: 200 { 201 const json_t *products; 202 struct GNUNET_JSON_Specification spec[] = { 203 GNUNET_JSON_spec_array_const ("products", 204 &products), 205 GNUNET_JSON_spec_end () 206 }; 207 208 if (GNUNET_OK != 209 GNUNET_JSON_parse (json, 210 spec, 211 NULL, NULL)) 212 { 213 pgr.hr.http_status = 0; 214 pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 215 break; 216 } 217 if (GNUNET_OK == 218 parse_products (products, 219 &pgr, 220 gpph)) 221 { 222 TALER_MERCHANT_get_private_products_cancel (gpph); 223 return; 224 } 225 pgr.hr.http_status = 0; 226 pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 227 break; 228 } 229 case MHD_HTTP_UNAUTHORIZED: 230 pgr.hr.ec = TALER_JSON_get_error_code (json); 231 pgr.hr.hint = TALER_JSON_get_error_hint (json); 232 break; 233 default: 234 pgr.hr.ec = TALER_JSON_get_error_code (json); 235 pgr.hr.hint = TALER_JSON_get_error_hint (json); 236 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 237 "Unexpected response code %u/%d\n", 238 (unsigned int) response_code, 239 (int) pgr.hr.ec); 240 break; 241 } 242 gpph->cb (gpph->cb_cls, 243 &pgr); 244 TALER_MERCHANT_get_private_products_cancel (gpph); 245 } 246 247 248 struct TALER_MERCHANT_GetPrivateProductsHandle * 249 TALER_MERCHANT_get_private_products_create ( 250 struct GNUNET_CURL_Context *ctx, 251 const char *url) 252 { 253 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph; 254 255 gpph = GNUNET_new (struct TALER_MERCHANT_GetPrivateProductsHandle); 256 gpph->ctx = ctx; 257 gpph->base_url = GNUNET_strdup (url); 258 gpph->limit = 20; 259 gpph->offset = INT64_MAX; 260 return gpph; 261 } 262 263 264 enum GNUNET_GenericReturnValue 265 TALER_MERCHANT_get_private_products_set_options_ ( 266 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph, 267 unsigned int num_options, 268 const struct TALER_MERCHANT_GetPrivateProductsOptionValue *options) 269 { 270 for (unsigned int i = 0; i < num_options; i++) 271 { 272 const struct TALER_MERCHANT_GetPrivateProductsOptionValue *opt = 273 &options[i]; 274 275 switch (opt->option) 276 { 277 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_END: 278 return GNUNET_OK; 279 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_LIMIT: 280 gpph->limit = opt->details.limit; 281 break; 282 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_OFFSET: 283 if (opt->details.offset > INT64_MAX) 284 { 285 GNUNET_break (0); 286 return GNUNET_NO; 287 } 288 gpph->offset = opt->details.offset; 289 gpph->have_offset = true; 290 break; 291 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_CATEGORY_FILTER: 292 GNUNET_free (gpph->category_filter); 293 if (NULL != opt->details.category_filter) 294 gpph->category_filter = GNUNET_strdup (opt->details.category_filter); 295 break; 296 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_NAME_FILTER: 297 GNUNET_free (gpph->name_filter); 298 if (NULL != opt->details.name_filter) 299 gpph->name_filter = GNUNET_strdup (opt->details.name_filter); 300 break; 301 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_DESCRIPTION_FILTER: 302 GNUNET_free (gpph->description_filter); 303 if (NULL != opt->details.description_filter) 304 gpph->description_filter = 305 GNUNET_strdup (opt->details.description_filter); 306 break; 307 case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_PRODUCT_GROUP_SERIAL: 308 gpph->product_group_serial = opt->details.product_group_serial; 309 gpph->have_product_group_serial = true; 310 break; 311 default: 312 GNUNET_break (0); 313 return GNUNET_NO; 314 } 315 } 316 return GNUNET_OK; 317 } 318 319 320 enum TALER_ErrorCode 321 TALER_MERCHANT_get_private_products_start ( 322 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph, 323 TALER_MERCHANT_GetPrivateProductsCallback cb, 324 TALER_MERCHANT_GET_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls) 325 { 326 CURL *eh; 327 328 gpph->cb = cb; 329 gpph->cb_cls = cb_cls; 330 { 331 char *cfilt = NULL; 332 char *nfilt = NULL; 333 char *dfilt = NULL; 334 char lbuf[30]; 335 char obuf[30]; 336 char pgsbuf[30]; 337 bool have_offset; 338 339 GNUNET_snprintf (lbuf, 340 sizeof (lbuf), 341 "%lld", 342 (long long) gpph->limit); 343 GNUNET_snprintf (obuf, 344 sizeof (obuf), 345 "%llu", 346 (unsigned long long) gpph->offset); 347 if (gpph->have_product_group_serial) 348 GNUNET_snprintf (pgsbuf, 349 sizeof (pgsbuf), 350 "%llu", 351 (unsigned long long) gpph->product_group_serial); 352 if (gpph->limit > 0) 353 { 354 have_offset = gpph->have_offset 355 && (0 != gpph->offset); 356 } 357 else 358 { 359 have_offset = gpph->have_offset 360 && (INT64_MAX != gpph->offset); 361 } 362 if (NULL != gpph->category_filter) 363 (void) GNUNET_STRINGS_urlencode (strlen (gpph->category_filter), 364 gpph->category_filter, 365 &cfilt); 366 if (NULL != gpph->name_filter) 367 (void) GNUNET_STRINGS_urlencode (strlen (gpph->name_filter), 368 gpph->name_filter, 369 &nfilt); 370 if (NULL != gpph->description_filter) 371 (void) GNUNET_STRINGS_urlencode (strlen (gpph->description_filter), 372 gpph->description_filter, 373 &dfilt); 374 gpph->url = TALER_url_join (gpph->base_url, 375 "private/products", 376 "limit", 377 (20 != gpph->limit) 378 ? lbuf 379 : NULL, 380 "offset", 381 have_offset 382 ? obuf 383 : NULL, 384 "category_filter", 385 cfilt, 386 "name_filter", 387 nfilt, 388 "description_filter", 389 dfilt, 390 "product_group_serial", 391 gpph->have_product_group_serial 392 ? pgsbuf 393 : NULL, 394 NULL); 395 GNUNET_free (cfilt); 396 GNUNET_free (nfilt); 397 GNUNET_free (dfilt); 398 } 399 if (NULL == gpph->url) 400 { 401 GNUNET_break (0); 402 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 403 } 404 eh = TALER_MERCHANT_curl_easy_get_ (gpph->url); 405 if (NULL == eh) 406 { 407 GNUNET_break (0); 408 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 409 } 410 gpph->job = GNUNET_CURL_job_add (gpph->ctx, 411 eh, 412 &handle_get_products_finished, 413 gpph); 414 if (NULL == gpph->job) 415 { 416 GNUNET_break (0); 417 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 418 } 419 return TALER_EC_NONE; 420 } 421 422 423 void 424 TALER_MERCHANT_get_private_products_cancel ( 425 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph) 426 { 427 if (NULL != gpph->job) 428 { 429 GNUNET_CURL_job_cancel (gpph->job); 430 gpph->job = NULL; 431 } 432 GNUNET_free (gpph->url); 433 GNUNET_free (gpph->category_filter); 434 GNUNET_free (gpph->name_filter); 435 GNUNET_free (gpph->description_filter); 436 GNUNET_free (gpph->base_url); 437 GNUNET_free (gpph); 438 } 439 440 441 /* end of merchant_api_get-private-products.c */