testing_api_cmd_testserver.c (10006B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file testing/testing_api_cmd_testserver.c 22 * @brief Implement a CMD to run an Testserver service for faking the legitimation service 23 * @author Priscilla HUANG 24 */ 25 #include "platform.h" 26 #include "taler/taler_json_lib.h" 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler/taler_testing_lib.h" 29 #include "taler/taler_mhd_lib.h" 30 #include "taler_merchant_testing_lib.h" 31 #include "taler_merchant_service.h" 32 #include <taler/taler_exchange_service.h> 33 34 /** 35 * State for the testserver CMD. 36 */ 37 struct TestserverState 38 { 39 40 /** 41 * Handle to the "testserver" service. 42 */ 43 struct MHD_Daemon *mhd; 44 45 /** 46 * Port to listen on. 47 */ 48 uint16_t port; 49 50 /** 51 * Array where we remember all of the requests this 52 * server answered. 53 */ 54 struct RequestCtx **rcs; 55 56 /** 57 * Length of the @a rcs array 58 */ 59 unsigned int rcs_length; 60 }; 61 62 63 struct RequestCtx 64 { 65 /** 66 * URL where we are redirect. 67 */ 68 char *url; 69 70 /** 71 * http method of the webhook. 72 */ 73 char *http_method; 74 75 /** 76 * header of the webhook. 77 */ 78 char *header; 79 80 /** 81 * body of the webhook. 82 */ 83 void *body; 84 85 /** 86 * size of the body 87 */ 88 size_t body_size; 89 90 /** 91 * Set to true when we are done with the request. 92 */ 93 bool done; 94 }; 95 96 97 /** 98 * A client has requested the given url using the given method 99 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 100 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback 101 * must call MHD callbacks to provide content to give back to the 102 * client and return an HTTP status code (i.e. #MHD_HTTP_OK, 103 * #MHD_HTTP_NOT_FOUND, etc.). 104 * 105 * @param cls argument given together with the function 106 * pointer when the handler was registered with MHD 107 * @param connection the connection being handled 108 * @param url the requested url 109 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 110 * #MHD_HTTP_METHOD_PUT, etc.) 111 * @param version the HTTP version string (i.e. 112 * MHD_HTTP_VERSION_1_1) 113 * @param upload_data the data being uploaded (excluding HEADERS, 114 * for a POST that fits into memory and that is encoded 115 * with a supported encoding, the POST data will NOT be 116 * given in upload_data and is instead available as 117 * part of MHD_get_connection_values(); very large POST 118 * data *will* be made available incrementally in 119 * @a upload_data) 120 * @param[in,out] upload_data_size set initially to the size of the 121 * @a upload_data provided; the method must update this 122 * value to the number of bytes NOT processed; 123 * @param[in,out] con_cls pointer that the callback can set to some 124 * address and that will be preserved by MHD for future 125 * calls for this request; since the access handler may 126 * be called many times (i.e., for a PUT/POST operation 127 * with plenty of upload data) this allows the application 128 * to easily associate some request-specific state. 129 * If necessary, this state can be cleaned up in the 130 * global MHD_RequestCompletedCallback (which 131 * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). 132 * Initially, `*con_cls` will be NULL. 133 * @return #MHD_YES if the connection was handled successfully, 134 * #MHD_NO if the socket must be closed due to a serious 135 * error while handling the request 136 */ 137 static MHD_RESULT 138 handler_cb (void *cls, 139 struct MHD_Connection *connection, 140 const char *url, 141 const char *method, 142 const char *version, 143 const char *upload_data, 144 size_t *upload_data_size, 145 void **con_cls) 146 { 147 struct TestserverState *ts = cls; 148 struct RequestCtx *rc = *con_cls; 149 150 (void) version; 151 if (NULL == rc) 152 { 153 const char *hdr; 154 155 rc = GNUNET_new (struct RequestCtx); 156 *con_cls = rc; 157 rc->http_method = GNUNET_strdup (method); 158 hdr = MHD_lookup_connection_value (connection, 159 MHD_HEADER_KIND, 160 "Taler-test-header"); 161 if (NULL != hdr) 162 rc->header = GNUNET_strdup (hdr); 163 if (NULL != url) 164 rc->url = GNUNET_strdup (url); 165 GNUNET_array_append (ts->rcs, 166 ts->rcs_length, 167 rc); 168 fprintf (stderr, 169 "Webhook called server at `%s' with header `%s'\n", 170 url, 171 hdr); 172 return MHD_YES; 173 } 174 if (0 == strcasecmp (method, 175 MHD_HTTP_METHOD_GET)) 176 { 177 json_t *reply; 178 179 reply = GNUNET_JSON_PACK ( 180 GNUNET_JSON_pack_string ( 181 "status", 182 "success")); 183 return TALER_MHD_reply_json_steal (connection, 184 reply, 185 MHD_HTTP_OK); 186 } 187 if (0 != strcasecmp (method, 188 MHD_HTTP_METHOD_POST)) 189 { 190 GNUNET_break (0); 191 return MHD_NO; 192 } 193 if (0 != *upload_data_size) 194 { 195 void *body; 196 197 body = GNUNET_malloc (rc->body_size + *upload_data_size); 198 GNUNET_memcpy (body, 199 rc->body, 200 rc->body_size); 201 GNUNET_free (rc->body); 202 GNUNET_memcpy (body + rc->body_size, 203 upload_data, 204 *upload_data_size); 205 rc->body = body; 206 rc->body_size += *upload_data_size; 207 *upload_data_size = 0; 208 return MHD_YES; 209 } 210 211 { 212 json_t *reply; 213 214 reply = GNUNET_JSON_PACK ( 215 GNUNET_JSON_pack_string ("something", 216 "good")); 217 return TALER_MHD_reply_json_steal (connection, 218 reply, 219 MHD_HTTP_OK); 220 } 221 } 222 223 224 static void 225 cleanup (void *cls, 226 struct MHD_Connection *connection, 227 void **con_cls, 228 enum MHD_RequestTerminationCode toe) 229 { 230 struct RequestCtx *rc = *con_cls; 231 232 (void) cls; 233 (void) connection; 234 (void) toe; 235 if (NULL == rc) 236 return; 237 rc->done = true; 238 } 239 240 241 /** 242 * Run the command. 243 * 244 * @param cls closure. 245 * @param cmd the command to execute. 246 * @param is the interpreter state. 247 */ 248 static void 249 testserver_run (void *cls, 250 const struct TALER_TESTING_Command *cmd, 251 struct TALER_TESTING_Interpreter *is) 252 { 253 struct TestserverState *ser = cls; 254 255 (void) cmd; 256 ser->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD, 257 ser->port, 258 NULL, NULL, 259 &handler_cb, ser, 260 MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL, 261 NULL); 262 if (NULL == ser->mhd) 263 { 264 GNUNET_break (0); 265 TALER_TESTING_interpreter_fail (is); 266 return; 267 } 268 TALER_TESTING_interpreter_next (is); 269 } 270 271 272 /** 273 * Cleanup the state from a "testserver" CMD, and possibly cancel a operation 274 * thereof. 275 * 276 * @param cls closure. 277 * @param cmd the command which is being cleaned up. 278 */ 279 static void 280 testserver_cleanup (void *cls, 281 const struct TALER_TESTING_Command *cmd) 282 { 283 struct TestserverState *ser = cls; 284 285 (void) cmd; 286 if (NULL != ser->mhd) 287 { 288 MHD_stop_daemon (ser->mhd); 289 ser->mhd = NULL; 290 } 291 for (unsigned int i = 0; i<ser->rcs_length; i++) 292 { 293 struct RequestCtx *rc = ser->rcs[i]; 294 295 GNUNET_assert (rc->done); 296 GNUNET_free (rc->url); 297 GNUNET_free (rc->http_method); 298 GNUNET_free (rc->header); 299 GNUNET_free (rc->body); 300 GNUNET_free (rc); 301 } 302 GNUNET_array_grow (ser->rcs, 303 ser->rcs_length, 304 0); 305 GNUNET_free (ser); 306 } 307 308 309 static enum GNUNET_GenericReturnValue 310 traits_testserver (void *cls, 311 const void **ret, 312 const char *trait, 313 unsigned int index) 314 { 315 struct TestserverState *ser = cls; 316 317 if (index >= ser->rcs_length) 318 return GNUNET_NO; 319 320 { 321 const struct RequestCtx *rc = ser->rcs[index]; 322 struct TALER_TESTING_Trait traits[] = { 323 TALER_TESTING_make_trait_urls (index, 324 rc->url), 325 TALER_TESTING_make_trait_http_methods (index, 326 rc->http_method), 327 TALER_TESTING_make_trait_http_header (index, 328 rc->header), 329 TALER_TESTING_make_trait_http_body (index, 330 rc->body), 331 TALER_TESTING_make_trait_http_body_size (index, 332 &rc->body_size), 333 TALER_TESTING_trait_end (), 334 }; 335 336 if (! rc->done) 337 return GNUNET_NO; 338 return TALER_TESTING_get_trait (traits, 339 ret, 340 trait, 341 index); 342 } 343 } 344 345 346 /** 347 * This function is used to start the web server. 348 * 349 * @param label command label 350 * @param port is the port of the web server 351 */ 352 struct TALER_TESTING_Command 353 TALER_TESTING_cmd_testserver (const char *label, 354 uint16_t port) 355 { 356 struct TestserverState *ser; 357 358 ser = GNUNET_new (struct TestserverState); 359 ser->port = port; 360 { 361 struct TALER_TESTING_Command cmd = { 362 .cls = ser, 363 .label = label, 364 .run = &testserver_run, 365 .cleanup = &testserver_cleanup, 366 .traits = &traits_testserver 367 }; 368 369 return cmd; 370 } 371 } 372 373 374 /* end of testing_api_cmd_checkserver.c */