taler-merchant-httpd_get-sessions-SESSION_ID.c (8730B)
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/lookup_order_by_fulfillment.h" 26 #include "merchant-database/lookup_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 TMH_force_get_sessions_ID_resume (void) 90 { 91 struct GetSessionContext *gsc; 92 93 while (NULL != (gsc = gsc_head)) 94 { 95 GNUNET_CONTAINER_DLL_remove (gsc_head, 96 gsc_tail, 97 gsc); 98 gsc->suspended = GNUNET_SYSERR; 99 MHD_resume_connection (gsc->sc.con); 100 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 101 } 102 } 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 gsc_cleanup (void *cls) 112 { 113 struct GetSessionContext *gsc = cls; 114 115 if (NULL != gsc->eh) 116 { 117 TALER_MERCHANTDB_event_listen_cancel (gsc->eh); 118 gsc->eh = NULL; 119 } 120 GNUNET_free (gsc); 121 } 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 resume_by_event (void *cls, 134 const void *extra, 135 size_t extra_size) 136 { 137 struct GetSessionContext *gsc = cls; 138 139 if (GNUNET_YES != gsc->suspended) 140 { 141 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 142 "Not suspended, ignoring event\n"); 143 return; /* duplicate event is possible */ 144 } 145 gsc->suspended = GNUNET_NO; 146 GNUNET_CONTAINER_DLL_remove (gsc_head, 147 gsc_tail, 148 gsc); 149 MHD_resume_connection (gsc->sc.con); 150 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 151 } 152 153 154 enum MHD_Result 155 TMH_get_sessions_ID ( 156 const struct TMH_RequestHandler *rh, 157 struct MHD_Connection *connection, 158 struct TMH_HandlerContext *hc) 159 { 160 struct GetSessionContext *gsc = hc->ctx; 161 struct TMH_MerchantInstance *mi = hc->instance; 162 char *order_id = NULL; 163 bool paid = false; 164 bool is_past; 165 166 GNUNET_assert (NULL != mi); 167 if (NULL == gsc) 168 { 169 gsc = GNUNET_new (struct GetSessionContext); 170 gsc->hc = hc; 171 hc->ctx = gsc; 172 hc->cc = &gsc_cleanup; 173 gsc->sc.con = connection; 174 gsc->fulfillment_url = MHD_lookup_connection_value ( 175 connection, 176 MHD_GET_ARGUMENT_KIND, 177 "fulfillment_url"); 178 if (NULL == gsc->fulfillment_url) 179 { 180 GNUNET_break_op (0); 181 return TALER_MHD_reply_with_error (connection, 182 MHD_HTTP_BAD_REQUEST, 183 TALER_EC_GENERIC_PARAMETER_MISSING, 184 "fulfillment_url"); 185 } 186 if (! TALER_is_web_url (gsc->fulfillment_url)) 187 { 188 GNUNET_break_op (0); 189 return TALER_MHD_reply_with_error (connection, 190 MHD_HTTP_BAD_REQUEST, 191 TALER_EC_GENERIC_PARAMETER_MALFORMED, 192 "fulfillment_url"); 193 } 194 195 TALER_MHD_parse_request_timeout (connection, 196 &gsc->sc.long_poll_timeout); 197 198 if (! GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout) ) 199 { 200 struct TMH_SessionEventP session_eh = { 201 .header.size = htons (sizeof (session_eh)), 202 .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), 203 .merchant_pub = gsc->hc->instance->merchant_pub 204 }; 205 206 GNUNET_CRYPTO_hash (hc->infix, 207 strlen (hc->infix), 208 &session_eh.h_session_id); 209 GNUNET_CRYPTO_hash (gsc->fulfillment_url, 210 strlen (gsc->fulfillment_url), 211 &session_eh.h_fulfillment_url); 212 gsc->eh 213 = 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 if (GNUNET_SYSERR == gsc->suspended) 223 return MHD_NO; /* close connection on service shutdown */ 224 225 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 qs = TALER_MERCHANTDB_lookup_order_by_fulfillment (TMH_db, 231 mi->settings.id, 232 gsc->fulfillment_url, 233 hc->infix, 234 false, 235 &order_id); 236 if (0 > qs) 237 { 238 GNUNET_break (0); 239 return TALER_MHD_reply_with_error ( 240 connection, 241 MHD_HTTP_INTERNAL_SERVER_ERROR, 242 TALER_EC_GENERIC_DB_FETCH_FAILED, 243 "lookup_order_by_fulfillment"); 244 } 245 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && 246 is_past) 247 { 248 return TALER_MHD_reply_with_error ( 249 connection, 250 MHD_HTTP_NOT_FOUND, 251 TALER_EC_MERCHANT_GENERIC_SESSION_UNKNOWN, 252 hc->infix); 253 } 254 } 255 256 /* Check if paid */ 257 if (NULL != order_id) 258 { 259 enum GNUNET_DB_QueryStatus qs; 260 struct TALER_PrivateContractHashP h_contract_terms; 261 262 qs = TALER_MERCHANTDB_lookup_order_status (TMH_db, 263 mi->settings.id, 264 order_id, 265 &h_contract_terms, 266 &paid); 267 if (0 >= qs) 268 { 269 GNUNET_break (0); 270 return TALER_MHD_reply_with_error ( 271 connection, 272 MHD_HTTP_INTERNAL_SERVER_ERROR, 273 TALER_EC_GENERIC_DB_FETCH_FAILED, 274 "lookup_order_status"); 275 } 276 } 277 278 if (paid) 279 { 280 enum MHD_Result ret; 281 282 ret = TALER_MHD_REPLY_JSON_PACK ( 283 connection, 284 MHD_HTTP_OK, 285 GNUNET_JSON_pack_string ("order_id", 286 order_id)); 287 GNUNET_free (order_id); 288 return ret; 289 } 290 291 if (is_past) 292 { 293 enum MHD_Result ret; 294 295 GNUNET_assert (NULL != order_id); 296 ret = TALER_MHD_REPLY_JSON_PACK ( 297 connection, 298 MHD_HTTP_ACCEPTED, 299 GNUNET_JSON_pack_string ("order_id", 300 order_id)); 301 GNUNET_free (order_id); 302 return ret; 303 } 304 305 GNUNET_free (order_id); 306 GNUNET_CONTAINER_DLL_insert (gsc_head, 307 gsc_tail, 308 gsc); 309 gsc->suspended = GNUNET_YES; 310 MHD_suspend_connection (gsc->sc.con); 311 return MHD_YES; 312 } 313 314 315 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */