Line data Source code
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/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 0 : parse_unit_entry (const json_t *value,
85 : struct TALER_MERCHANT_UnitEntry *ue)
86 : {
87 : struct GNUNET_JSON_Specification spec[] = {
88 0 : GNUNET_JSON_spec_string ("unit",
89 : &ue->unit),
90 0 : GNUNET_JSON_spec_string ("unit_name_long",
91 : &ue->unit_name_long),
92 0 : GNUNET_JSON_spec_string ("unit_name_short",
93 : &ue->unit_name_short),
94 0 : GNUNET_JSON_spec_mark_optional (
95 : GNUNET_JSON_spec_object_const ("unit_name_long_i18n",
96 : &ue->unit_name_long_i18n),
97 : NULL),
98 0 : GNUNET_JSON_spec_mark_optional (
99 : GNUNET_JSON_spec_object_const ("unit_name_short_i18n",
100 : &ue->unit_name_short_i18n),
101 : NULL),
102 0 : GNUNET_JSON_spec_bool ("unit_allow_fraction",
103 : &ue->unit_allow_fraction),
104 0 : GNUNET_JSON_spec_uint32 ("unit_precision_level",
105 : &ue->unit_precision_level),
106 0 : GNUNET_JSON_spec_bool ("unit_active",
107 : &ue->unit_active),
108 0 : GNUNET_JSON_spec_bool ("unit_builtin",
109 : &ue->unit_builtin),
110 0 : GNUNET_JSON_spec_uint64 ("unit_serial",
111 : &ue->unit_serial),
112 0 : GNUNET_JSON_spec_end ()
113 : };
114 :
115 0 : memset (ue,
116 : 0,
117 : sizeof (*ue));
118 0 : if (GNUNET_OK !=
119 0 : GNUNET_JSON_parse (value,
120 : spec,
121 : NULL,
122 : NULL))
123 : {
124 0 : GNUNET_break_op (0);
125 0 : return GNUNET_SYSERR;
126 : }
127 0 : 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 0 : parse_units (const json_t *json,
141 : const json_t *units,
142 : struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh)
143 : {
144 0 : size_t len = json_array_size (units);
145 :
146 0 : if (len > MAX_UNITS)
147 : {
148 0 : GNUNET_break_op (0);
149 0 : return GNUNET_SYSERR;
150 : }
151 :
152 0 : {
153 0 : struct TALER_MERCHANT_UnitEntry entries[GNUNET_NZL (len)];
154 : size_t idx;
155 : json_t *value;
156 :
157 0 : json_array_foreach (units, idx, value) {
158 0 : if (GNUNET_OK !=
159 0 : parse_unit_entry (value,
160 : &entries[idx]))
161 : {
162 0 : GNUNET_break_op (0);
163 0 : return GNUNET_SYSERR;
164 : }
165 : }
166 : {
167 0 : struct TALER_MERCHANT_GetPrivateUnitsResponse ugr = {
168 : .hr.http_status = MHD_HTTP_OK,
169 : .hr.reply = json,
170 : .details.ok.units = entries,
171 0 : .details.ok.units_length = (unsigned int) len
172 : };
173 :
174 0 : gpuh->cb (gpuh->cb_cls,
175 : &ugr);
176 : }
177 : }
178 0 : 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 0 : handle_get_units_finished (void *cls,
192 : long response_code,
193 : const void *response)
194 : {
195 0 : struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh = cls;
196 0 : const json_t *json = response;
197 0 : struct TALER_MERCHANT_GetPrivateUnitsResponse ugr = {
198 0 : .hr.http_status = (unsigned int) response_code,
199 : .hr.reply = json
200 : };
201 :
202 0 : gpuh->job = NULL;
203 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
204 : "Got /private/units response with status code %u\n",
205 : (unsigned int) response_code);
206 0 : switch (response_code)
207 : {
208 0 : case MHD_HTTP_OK:
209 : {
210 : const json_t *units;
211 : struct GNUNET_JSON_Specification spec[] = {
212 0 : GNUNET_JSON_spec_array_const ("units",
213 : &units),
214 0 : GNUNET_JSON_spec_end ()
215 : };
216 :
217 0 : if (GNUNET_OK !=
218 0 : GNUNET_JSON_parse (json,
219 : spec,
220 : NULL, NULL))
221 : {
222 0 : GNUNET_break_op (0);
223 0 : ugr.hr.http_status = 0;
224 0 : ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
225 0 : break;
226 : }
227 0 : if (GNUNET_OK ==
228 0 : parse_units (json,
229 : units,
230 : gpuh))
231 : {
232 0 : TALER_MERCHANT_get_private_units_cancel (gpuh);
233 0 : return;
234 : }
235 0 : ugr.hr.http_status = 0;
236 0 : ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
237 0 : break;
238 : }
239 0 : case MHD_HTTP_UNAUTHORIZED:
240 : case MHD_HTTP_FORBIDDEN:
241 : case MHD_HTTP_NOT_FOUND:
242 : case MHD_HTTP_CONFLICT:
243 0 : ugr.hr.ec = TALER_JSON_get_error_code (json);
244 0 : ugr.hr.hint = TALER_JSON_get_error_hint (json);
245 0 : break;
246 0 : case 0:
247 0 : ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
248 0 : break;
249 0 : default:
250 0 : ugr.hr.ec = TALER_JSON_get_error_code (json);
251 0 : ugr.hr.hint = TALER_JSON_get_error_hint (json);
252 0 : 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 0 : break;
257 : }
258 0 : gpuh->cb (gpuh->cb_cls,
259 : &ugr);
260 0 : TALER_MERCHANT_get_private_units_cancel (gpuh);
261 : }
262 :
263 :
264 : struct TALER_MERCHANT_GetPrivateUnitsHandle *
265 0 : 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 0 : gpuh = GNUNET_new (struct TALER_MERCHANT_GetPrivateUnitsHandle);
272 0 : gpuh->ctx = ctx;
273 0 : gpuh->base_url = GNUNET_strdup (url);
274 0 : return gpuh;
275 : }
276 :
277 :
278 : enum TALER_ErrorCode
279 0 : 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 0 : gpuh->cb = cb;
287 0 : gpuh->cb_cls = cb_cls;
288 0 : gpuh->url = TALER_url_join (gpuh->base_url,
289 : "private/units",
290 : NULL);
291 0 : if (NULL == gpuh->url)
292 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
293 0 : eh = TALER_MERCHANT_curl_easy_get_ (gpuh->url);
294 0 : if (NULL == eh)
295 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
296 0 : gpuh->job = GNUNET_CURL_job_add (gpuh->ctx,
297 : eh,
298 : &handle_get_units_finished,
299 : gpuh);
300 0 : if (NULL == gpuh->job)
301 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
302 0 : return TALER_EC_NONE;
303 : }
304 :
305 :
306 : void
307 0 : TALER_MERCHANT_get_private_units_cancel (
308 : struct TALER_MERCHANT_GetPrivateUnitsHandle *gpuh)
309 : {
310 0 : if (NULL != gpuh->job)
311 : {
312 0 : GNUNET_CURL_job_cancel (gpuh->job);
313 0 : gpuh->job = NULL;
314 : }
315 0 : GNUNET_free (gpuh->url);
316 0 : GNUNET_free (gpuh->base_url);
317 0 : GNUNET_free (gpuh);
318 0 : }
319 :
320 :
321 : /* end of merchant_api_get-private-units.c */
|