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