Line data Source code
1 : /*
2 : This file is part of TALER
3 : (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 Affero General Public License as published by the Free Software
7 : Foundation; either version 3, 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/backend/taler-merchant-httpd_get-sessions-SESSION_ID.c
18 : * @brief implement GET /session/$ID
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "taler-merchant-httpd_get-sessions-SESSION_ID.h"
23 : #include <taler/taler_json_lib.h>
24 : #include <taler/taler_dbevents.h>
25 : #include "merchant-database/get_order_by_fulfillment.h"
26 : #include "merchant-database/get_order_status.h"
27 : #include "merchant-database/event_listen.h"
28 :
29 :
30 : /**
31 : * Context for a get sessions request.
32 : */
33 : struct GetSessionContext
34 : {
35 : /**
36 : * Kept in a DLL.
37 : */
38 : struct GetSessionContext *next;
39 :
40 : /**
41 : * Kept in a DLL.
42 : */
43 : struct GetSessionContext *prev;
44 :
45 : /**
46 : * Request context.
47 : */
48 : struct TMH_HandlerContext *hc;
49 :
50 : /**
51 : * Entry in the #resume_timeout_heap for this check payment, if we are
52 : * suspended.
53 : */
54 : struct TMH_SuspendedConnection sc;
55 :
56 : /**
57 : * Fulfillment URL from the HTTP request.
58 : */
59 : const char *fulfillment_url;
60 :
61 : /**
62 : * Database event we are waiting on to be resuming on payment.
63 : */
64 : struct GNUNET_DB_EventHandler *eh;
65 :
66 : /**
67 : * Did we suspend @a connection and are thus in
68 : * the #gsc_head DLL (#GNUNET_YES). Set to
69 : * #GNUNET_NO if we are not suspended, and to
70 : * #GNUNET_SYSERR if we should close the connection
71 : * without a response due to shutdown.
72 : */
73 : enum GNUNET_GenericReturnValue suspended;
74 : };
75 :
76 :
77 : /**
78 : * Kept in a DLL.
79 : */
80 : static struct GetSessionContext *gsc_head;
81 :
82 : /**
83 : * Kept in a DLL.
84 : */
85 : static struct GetSessionContext *gsc_tail;
86 :
87 :
88 : void
89 20 : TMH_force_get_sessions_ID_resume (void)
90 : {
91 : struct GetSessionContext *gsc;
92 :
93 20 : while (NULL != (gsc = gsc_head))
94 : {
95 0 : GNUNET_CONTAINER_DLL_remove (gsc_head,
96 : gsc_tail,
97 : gsc);
98 0 : gsc->suspended = GNUNET_SYSERR;
99 0 : MHD_resume_connection (gsc->sc.con);
100 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
101 : }
102 20 : }
103 :
104 :
105 : /**
106 : * Cleanup helper for TMH_get_session_ID().
107 : *
108 : * @param cls must be a `struct GetSessionContext`
109 : */
110 : static void
111 0 : gsc_cleanup (void *cls)
112 : {
113 0 : struct GetSessionContext *gsc = cls;
114 :
115 0 : if (NULL != gsc->eh)
116 : {
117 0 : TALER_MERCHANTDB_event_listen_cancel (gsc->eh);
118 0 : gsc->eh = NULL;
119 : }
120 0 : GNUNET_free (gsc);
121 0 : }
122 :
123 :
124 : /**
125 : * We have received a trigger from the database
126 : * that we should (possibly) resume the request.
127 : *
128 : * @param cls a `struct GetOrderData` to resume
129 : * @param extra string encoding refund amount (or NULL)
130 : * @param extra_size number of bytes in @a extra
131 : */
132 : static void
133 0 : resume_by_event (void *cls,
134 : const void *extra,
135 : size_t extra_size)
136 : {
137 0 : struct GetSessionContext *gsc = cls;
138 :
139 0 : if (GNUNET_YES != gsc->suspended)
140 : {
141 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
142 : "Not suspended, ignoring event\n");
143 0 : return; /* duplicate event is possible */
144 : }
145 0 : gsc->suspended = GNUNET_NO;
146 0 : GNUNET_CONTAINER_DLL_remove (gsc_head,
147 : gsc_tail,
148 : gsc);
149 0 : MHD_resume_connection (gsc->sc.con);
150 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
151 : }
152 :
153 :
154 : enum MHD_Result
155 0 : TMH_get_sessions_ID (
156 : const struct TMH_RequestHandler *rh,
157 : struct MHD_Connection *connection,
158 : struct TMH_HandlerContext *hc)
159 : {
160 0 : struct GetSessionContext *gsc = hc->ctx;
161 0 : struct TMH_MerchantInstance *mi = hc->instance;
162 0 : char *order_id = NULL;
163 0 : bool paid = false;
164 : bool is_past;
165 :
166 0 : GNUNET_assert (NULL != mi);
167 0 : if (NULL == gsc)
168 : {
169 0 : gsc = GNUNET_new (struct GetSessionContext);
170 0 : gsc->hc = hc;
171 0 : hc->ctx = gsc;
172 0 : hc->cc = &gsc_cleanup;
173 0 : gsc->sc.con = connection;
174 0 : gsc->fulfillment_url = MHD_lookup_connection_value (
175 : connection,
176 : MHD_GET_ARGUMENT_KIND,
177 : "fulfillment_url");
178 0 : if (NULL == gsc->fulfillment_url)
179 : {
180 0 : GNUNET_break_op (0);
181 0 : return TALER_MHD_reply_with_error (connection,
182 : MHD_HTTP_BAD_REQUEST,
183 : TALER_EC_GENERIC_PARAMETER_MISSING,
184 : "fulfillment_url");
185 : }
186 0 : if (! TALER_is_web_url (gsc->fulfillment_url))
187 : {
188 0 : GNUNET_break_op (0);
189 0 : return TALER_MHD_reply_with_error (connection,
190 : MHD_HTTP_BAD_REQUEST,
191 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
192 : "fulfillment_url");
193 : }
194 :
195 0 : TALER_MHD_parse_request_timeout (connection,
196 : &gsc->sc.long_poll_timeout);
197 :
198 0 : if (! GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout) )
199 : {
200 0 : struct TMH_SessionEventP session_eh = {
201 0 : .header.size = htons (sizeof (session_eh)),
202 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
203 0 : .merchant_pub = gsc->hc->instance->merchant_pub
204 : };
205 :
206 0 : GNUNET_CRYPTO_hash (hc->infix,
207 0 : strlen (hc->infix),
208 : &session_eh.h_session_id);
209 0 : GNUNET_CRYPTO_hash (gsc->fulfillment_url,
210 : strlen (gsc->fulfillment_url),
211 : &session_eh.h_fulfillment_url);
212 : gsc->eh
213 0 : = TALER_MERCHANTDB_event_listen (
214 : TMH_db,
215 : &session_eh.header,
216 : GNUNET_TIME_absolute_get_remaining (gsc->sc.long_poll_timeout),
217 : &resume_by_event,
218 : gsc);
219 : }
220 : } /* end first-time initialization (NULL == gsc) */
221 :
222 0 : if (GNUNET_SYSERR == gsc->suspended)
223 0 : return MHD_NO; /* close connection on service shutdown */
224 :
225 0 : is_past = GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout);
226 : /* figure out order_id */
227 : {
228 : enum GNUNET_DB_QueryStatus qs;
229 :
230 0 : qs = TALER_MERCHANTDB_get_order_by_fulfillment (TMH_db,
231 0 : mi->settings.id,
232 : gsc->fulfillment_url,
233 0 : hc->infix,
234 : false,
235 : &order_id);
236 0 : if (0 > qs)
237 : {
238 0 : GNUNET_break (0);
239 0 : return TALER_MHD_reply_with_error (
240 : connection,
241 : MHD_HTTP_INTERNAL_SERVER_ERROR,
242 : TALER_EC_GENERIC_DB_FETCH_FAILED,
243 : "get_order_by_fulfillment");
244 : }
245 0 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
246 : is_past)
247 : {
248 0 : return TALER_MHD_reply_with_error (
249 : connection,
250 : MHD_HTTP_NOT_FOUND,
251 : TALER_EC_MERCHANT_GENERIC_SESSION_UNKNOWN,
252 0 : hc->infix);
253 : }
254 : }
255 :
256 : /* Check if paid */
257 0 : if (NULL != order_id)
258 : {
259 : enum GNUNET_DB_QueryStatus qs;
260 : struct TALER_PrivateContractHashP h_contract_terms;
261 :
262 0 : qs = TALER_MERCHANTDB_get_order_status (TMH_db,
263 0 : mi->settings.id,
264 : order_id,
265 : &h_contract_terms,
266 : &paid);
267 0 : if (0 >= qs)
268 : {
269 0 : GNUNET_break (0);
270 0 : GNUNET_free (order_id);
271 0 : return TALER_MHD_reply_with_error (
272 : connection,
273 : MHD_HTTP_INTERNAL_SERVER_ERROR,
274 : TALER_EC_GENERIC_DB_FETCH_FAILED,
275 : "get_order_status");
276 : }
277 : }
278 :
279 0 : if (paid)
280 : {
281 : enum MHD_Result ret;
282 :
283 0 : ret = TALER_MHD_REPLY_JSON_PACK (
284 : connection,
285 : MHD_HTTP_OK,
286 : GNUNET_JSON_pack_string ("order_id",
287 : order_id));
288 0 : GNUNET_free (order_id);
289 0 : return ret;
290 : }
291 :
292 0 : if (is_past)
293 : {
294 : enum MHD_Result ret;
295 :
296 0 : GNUNET_assert (NULL != order_id);
297 0 : ret = TALER_MHD_REPLY_JSON_PACK (
298 : connection,
299 : MHD_HTTP_ACCEPTED,
300 : GNUNET_JSON_pack_string ("order_id",
301 : order_id));
302 0 : GNUNET_free (order_id);
303 0 : return ret;
304 : }
305 :
306 0 : GNUNET_free (order_id);
307 0 : GNUNET_CONTAINER_DLL_insert (gsc_head,
308 : gsc_tail,
309 : gsc);
310 0 : gsc->suspended = GNUNET_YES;
311 0 : MHD_suspend_connection (gsc->sc.con);
312 0 : return MHD_YES;
313 : }
314 :
315 :
316 : /* end of taler-merchant-httpd_get-sessions-SESSION_ID.c */
|