Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2025 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
7 : Software 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
12 : details.
13 :
14 : You should have received a copy of the GNU Lesser General Public License along
15 : with TALER; see the file COPYING.LGPL. If not, see
16 : <http://www.gnu.org/licenses/>
17 : */
18 : /**
19 : * @file merchant_api_get_units.c
20 : * @brief Implementation of GET /private/units
21 : * @author Bohdan Potuzhnyi
22 : */
23 : #include "platform.h"
24 : #include <curl/curl.h>
25 : #include <jansson.h>
26 : #include <microhttpd.h>
27 : #include <gnunet/gnunet_util_lib.h>
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_merchant_service.h"
30 : #include "merchant_api_curl_defaults.h"
31 : #include <taler/taler_json_lib.h>
32 :
33 :
34 : /**
35 : * Maximum number of units returned in a single response.
36 : */
37 : #define MAX_UNITS 1024
38 :
39 :
40 : /**
41 : * Handle for a GET /private/units operation.
42 : */
43 : struct TALER_MERCHANT_UnitsGetHandle
44 : {
45 : /**
46 : * Fully qualified request URL.
47 : */
48 : char *url;
49 :
50 : /**
51 : * In-flight job handle.
52 : */
53 : struct GNUNET_CURL_Job *job;
54 :
55 : /**
56 : * Callback to invoke with the outcome.
57 : */
58 : TALER_MERCHANT_UnitsGetCallback cb;
59 :
60 : /**
61 : * Closure for @e cb.
62 : */
63 : void *cb_cls;
64 :
65 : /**
66 : * Execution context.
67 : */
68 : struct GNUNET_CURL_Context *ctx;
69 : };
70 :
71 :
72 : /**
73 : * Parse an individual unit entry from @a value.
74 : *
75 : * @param value JSON object describing the unit
76 : * @param[out] ue set to the parsed values
77 : * @return #GNUNET_OK on success
78 : */
79 : static enum GNUNET_GenericReturnValue
80 148 : parse_unit_entry (const json_t *value,
81 : struct TALER_MERCHANT_UnitEntry *ue)
82 : {
83 : const char *unit;
84 : const char *unit_name_long;
85 : const char *unit_name_short;
86 148 : const json_t *unit_name_long_i18n = NULL;
87 148 : const json_t *unit_name_short_i18n = NULL;
88 : bool unit_allow_fraction;
89 : bool unit_active;
90 : bool unit_builtin;
91 : uint32_t unit_precision_level;
92 : struct GNUNET_JSON_Specification spec[] = {
93 148 : GNUNET_JSON_spec_string ("unit",
94 : &unit),
95 148 : GNUNET_JSON_spec_string ("unit_name_long",
96 : &unit_name_long),
97 148 : GNUNET_JSON_spec_string ("unit_name_short",
98 : &unit_name_short),
99 148 : GNUNET_JSON_spec_mark_optional (
100 : GNUNET_JSON_spec_object_const ("unit_name_long_i18n",
101 : &unit_name_long_i18n),
102 : NULL),
103 148 : GNUNET_JSON_spec_mark_optional (
104 : GNUNET_JSON_spec_object_const ("unit_name_short_i18n",
105 : &unit_name_short_i18n),
106 : NULL),
107 148 : GNUNET_JSON_spec_bool ("unit_allow_fraction",
108 : &unit_allow_fraction),
109 148 : GNUNET_JSON_spec_uint32 ("unit_precision_level",
110 : &unit_precision_level),
111 148 : GNUNET_JSON_spec_bool ("unit_active",
112 : &unit_active),
113 148 : GNUNET_JSON_spec_bool ("unit_builtin",
114 : &unit_builtin),
115 148 : GNUNET_JSON_spec_end ()
116 : };
117 :
118 148 : if (GNUNET_OK !=
119 148 : GNUNET_JSON_parse (value,
120 : spec,
121 : NULL,
122 : NULL))
123 : {
124 0 : GNUNET_break_op (0);
125 0 : GNUNET_JSON_parse_free (spec);
126 0 : return GNUNET_SYSERR;
127 : }
128 148 : GNUNET_JSON_parse_free (spec);
129 148 : ue->unit = unit;
130 148 : ue->unit_name_long = unit_name_long;
131 148 : ue->unit_name_short = unit_name_short;
132 148 : ue->unit_name_long_i18n = unit_name_long_i18n;
133 148 : ue->unit_name_short_i18n = unit_name_short_i18n;
134 148 : ue->unit_allow_fraction = unit_allow_fraction;
135 148 : ue->unit_precision_level = unit_precision_level;
136 148 : ue->unit_active = unit_active;
137 148 : ue->unit_builtin = unit_builtin;
138 148 : return GNUNET_OK;
139 : }
140 :
141 :
142 : /**
143 : * Parse the list of units from @a units and call the callback.
144 : *
145 : * @param json complete response JSON
146 : * @param units array of units
147 : * @param ugh ongoing operation handle
148 : * @return #GNUNET_OK on success
149 : */
150 : static enum GNUNET_GenericReturnValue
151 4 : parse_units (const json_t *json,
152 : const json_t *units,
153 : struct TALER_MERCHANT_UnitsGetHandle *ugh)
154 : {
155 : size_t len;
156 :
157 4 : len = json_array_size (units);
158 4 : if (len > MAX_UNITS)
159 : {
160 0 : GNUNET_break_op (0);
161 0 : return GNUNET_SYSERR;
162 : }
163 :
164 4 : {
165 4 : struct TALER_MERCHANT_UnitEntry entries[GNUNET_NZL (len)];
166 : size_t idx;
167 : json_t *value;
168 :
169 152 : json_array_foreach (units, idx, value) {
170 148 : if (GNUNET_OK !=
171 148 : parse_unit_entry (value,
172 : &entries[idx]))
173 : {
174 0 : GNUNET_break_op (0);
175 0 : return GNUNET_SYSERR;
176 : }
177 : }
178 : {
179 4 : struct TALER_MERCHANT_UnitsGetResponse ugr = {
180 : .hr.http_status = MHD_HTTP_OK,
181 : .hr.reply = json,
182 : .details = {
183 : .ok = {
184 : .units = entries,
185 4 : .units_length = (unsigned int) len
186 : }
187 :
188 :
189 : }
190 :
191 :
192 : };
193 :
194 4 : ugh->cb (ugh->cb_cls,
195 : &ugr);
196 : }
197 : }
198 4 : return GNUNET_OK;
199 : }
200 :
201 :
202 : /**
203 : * Called when the HTTP transfer finishes.
204 : *
205 : * @param cls closure, the operation handle
206 : * @param response_code HTTP status (0 on network errors)
207 : * @param response parsed JSON body (NULL if parsing failed)
208 : */
209 : static void
210 4 : handle_get_units_finished (void *cls,
211 : long response_code,
212 : const void *response)
213 : {
214 4 : struct TALER_MERCHANT_UnitsGetHandle *ugh = cls;
215 4 : const json_t *json = response;
216 4 : struct TALER_MERCHANT_UnitsGetResponse ugr = {
217 4 : .hr.http_status = (unsigned int) response_code,
218 : .hr.reply = json
219 : };
220 :
221 4 : ugh->job = NULL;
222 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
223 : "GET /private/units finished with status %u\n",
224 : (unsigned int) response_code);
225 4 : switch (response_code)
226 : {
227 4 : case MHD_HTTP_OK:
228 : {
229 : const json_t *units;
230 : struct GNUNET_JSON_Specification spec[] = {
231 4 : GNUNET_JSON_spec_array_const ("units",
232 : &units),
233 4 : GNUNET_JSON_spec_end ()
234 : };
235 :
236 4 : if (GNUNET_OK !=
237 4 : GNUNET_JSON_parse (json,
238 : spec,
239 : NULL,
240 : NULL))
241 : {
242 0 : GNUNET_break_op (0);
243 0 : ugr.hr.http_status = 0;
244 0 : ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
245 0 : break;
246 : }
247 4 : if (GNUNET_OK ==
248 4 : parse_units (json,
249 : units,
250 : ugh))
251 : {
252 4 : TALER_MERCHANT_units_get_cancel (ugh);
253 4 : return;
254 : }
255 0 : ugr.hr.http_status = 0;
256 0 : ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
257 0 : break;
258 : }
259 0 : case MHD_HTTP_UNAUTHORIZED:
260 : case MHD_HTTP_FORBIDDEN:
261 : case MHD_HTTP_NOT_FOUND:
262 : case MHD_HTTP_CONFLICT:
263 0 : ugr.hr.ec = TALER_JSON_get_error_code (json);
264 0 : ugr.hr.hint = TALER_JSON_get_error_hint (json);
265 0 : break;
266 0 : case 0:
267 0 : ugr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
268 0 : break;
269 0 : default:
270 0 : ugr.hr.ec = TALER_JSON_get_error_code (json);
271 0 : ugr.hr.hint = TALER_JSON_get_error_hint (json);
272 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
273 : "Unexpected response code %u/%d for GET /private/units\n",
274 : (unsigned int) response_code,
275 : (int) ugr.hr.ec);
276 0 : break;
277 : }
278 0 : ugh->cb (ugh->cb_cls,
279 : &ugr);
280 0 : TALER_MERCHANT_units_get_cancel (ugh);
281 : }
282 :
283 :
284 : struct TALER_MERCHANT_UnitsGetHandle *
285 4 : TALER_MERCHANT_units_get (struct GNUNET_CURL_Context *ctx,
286 : const char *backend_url,
287 : TALER_MERCHANT_UnitsGetCallback cb,
288 : void *cb_cls)
289 : {
290 : struct TALER_MERCHANT_UnitsGetHandle *ugh;
291 : CURL *eh;
292 :
293 4 : ugh = GNUNET_new (struct TALER_MERCHANT_UnitsGetHandle);
294 4 : ugh->ctx = ctx;
295 4 : ugh->cb = cb;
296 4 : ugh->cb_cls = cb_cls;
297 4 : ugh->url = TALER_url_join (backend_url,
298 : "private/units",
299 : NULL);
300 4 : if (NULL == ugh->url)
301 : {
302 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
303 : "Failed to build /private/units URL\n");
304 0 : GNUNET_free (ugh);
305 0 : return NULL;
306 : }
307 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
308 : "Requesting URL '%s'\n",
309 : ugh->url);
310 4 : eh = TALER_MERCHANT_curl_easy_get_ (ugh->url);
311 4 : ugh->job = GNUNET_CURL_job_add (ctx,
312 : eh,
313 : &handle_get_units_finished,
314 : ugh);
315 4 : return ugh;
316 : }
317 :
318 :
319 : void
320 4 : TALER_MERCHANT_units_get_cancel (struct TALER_MERCHANT_UnitsGetHandle *ugh)
321 : {
322 4 : if (NULL != ugh->job)
323 0 : GNUNET_CURL_job_cancel (ugh->job);
324 4 : GNUNET_free (ugh->url);
325 4 : GNUNET_free (ugh);
326 4 : }
327 :
328 :
329 : /* end of merchant_api_get_units.c */
|