/* This file is part of TALER Copyright (C) 2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file testing/testing_api_cmd_testserver.c * @brief Implement a CMD to run an Testserver service for faking the legitimation service * @author Priscilla HUANG */ #include "platform.h" #include "taler/taler_json_lib.h" #include #include "taler/taler_testing_lib.h" #include "taler/taler_mhd_lib.h" #include "taler_merchant_testing_lib.h" #include "taler_merchant_service.h" #include /** * State for the testserver CMD. */ struct TestserverState { /** * Handle to the "testserver" service. */ struct MHD_Daemon *mhd; /** * Port to listen on. */ uint16_t port; /** * Array where we remember all of the requests this * server answered. */ struct RequestCtx **rcs; /** * Length of the @a rcs array */ unsigned int rcs_length; }; struct RequestCtx { /** * URL where we are redirect. */ char *url; /** * http method of the webhook. */ char *http_method; /** * header of the webhook. */ char *header; /** * body of the webhook. */ void *body; /** * size of the body */ size_t body_size; /** * Set to true when we are done with the request. */ bool done; }; /** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback * must call MHD callbacks to provide content to give back to the * client and return an HTTP status code (i.e. #MHD_HTTP_OK, * #MHD_HTTP_NOT_FOUND, etc.). * * @param cls argument given together with the function * pointer when the handler was registered with MHD * @param connection the connection being handled * @param url the requested url * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, * #MHD_HTTP_METHOD_PUT, etc.) * @param version the HTTP version string (i.e. * MHD_HTTP_VERSION_1_1) * @param upload_data the data being uploaded (excluding HEADERS, * for a POST that fits into memory and that is encoded * with a supported encoding, the POST data will NOT be * given in upload_data and is instead available as * part of MHD_get_connection_values(); very large POST * data *will* be made available incrementally in * @a upload_data) * @param[in,out] upload_data_size set initially to the size of the * @a upload_data provided; the method must update this * value to the number of bytes NOT processed; * @param[in,out] con_cls pointer that the callback can set to some * address and that will be preserved by MHD for future * calls for this request; since the access handler may * be called many times (i.e., for a PUT/POST operation * with plenty of upload data) this allows the application * to easily associate some request-specific state. * If necessary, this state can be cleaned up in the * global MHD_RequestCompletedCallback (which * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). * Initially, `*con_cls` will be NULL. * @return #MHD_YES if the connection was handled successfully, * #MHD_NO if the socket must be closed due to a serious * error while handling the request */ static MHD_RESULT handler_cb (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { struct TestserverState *ts = cls; struct RequestCtx *rc = *con_cls; (void) version; if (NULL == rc) { const char *hdr; rc = GNUNET_new (struct RequestCtx); *con_cls = rc; rc->http_method = GNUNET_strdup (method); hdr = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Taler-test-header"); if (NULL != hdr) rc->header = GNUNET_strdup (hdr); if (NULL != url) rc->url = GNUNET_strdup (url); GNUNET_array_append (ts->rcs, ts->rcs_length, rc); fprintf (stderr, "Webhook called server at `%s' with header `%s'\n", url, hdr); return MHD_YES; } if (0 == strcasecmp (method, MHD_HTTP_METHOD_GET)) { json_t *reply; reply = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ( "status", "success")); return TALER_MHD_reply_json_steal (connection, reply, MHD_HTTP_OK); } if (0 != strcasecmp (method, MHD_HTTP_METHOD_POST)) { GNUNET_break (0); return MHD_NO; } if (0 != *upload_data_size) { void *body; body = GNUNET_malloc (rc->body_size + *upload_data_size); GNUNET_memcpy (body, rc->body, rc->body_size); GNUNET_free (rc->body); GNUNET_memcpy (body + rc->body_size, upload_data, *upload_data_size); rc->body = body; rc->body_size += *upload_data_size; *upload_data_size = 0; return MHD_YES; } { json_t *reply; reply = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("something", "good")); return TALER_MHD_reply_json_steal (connection, reply, MHD_HTTP_OK); } } static void cleanup (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { struct RequestCtx *rc = *con_cls; (void) cls; (void) connection; (void) toe; if (NULL == rc) return; rc->done = true; } /** * Run the command. * * @param cls closure. * @param cmd the command to execute. * @param is the interpreter state. */ static void testserver_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct TestserverState *ser = cls; (void) cmd; ser->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD, ser->port, NULL, NULL, &handler_cb, ser, MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL, NULL); if (NULL == ser->mhd) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } TALER_TESTING_interpreter_next (is); } /** * Cleanup the state from a "testserver" CMD, and possibly cancel a operation * thereof. * * @param cls closure. * @param cmd the command which is being cleaned up. */ static void testserver_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct TestserverState *ser = cls; (void) cmd; if (NULL != ser->mhd) { MHD_stop_daemon (ser->mhd); ser->mhd = NULL; } for (unsigned int i = 0; ircs_length; i++) { struct RequestCtx *rc = ser->rcs[i]; GNUNET_assert (rc->done); GNUNET_free (rc->url); GNUNET_free (rc->http_method); GNUNET_free (rc->header); GNUNET_free (rc->body); GNUNET_free (rc); } GNUNET_array_grow (ser->rcs, ser->rcs_length, 0); GNUNET_free (ser); } static enum GNUNET_GenericReturnValue traits_testserver (void *cls, const void **ret, const char *trait, unsigned int index) { struct TestserverState *ser = cls; if (index >= ser->rcs_length) return GNUNET_NO; { const struct RequestCtx *rc = ser->rcs[index]; struct TALER_TESTING_Trait traits[] = { TALER_TESTING_make_trait_urls (index, rc->url), TALER_TESTING_make_trait_http_methods (index, rc->http_method), TALER_TESTING_make_trait_http_header (index, rc->header), TALER_TESTING_make_trait_http_body (index, rc->body), TALER_TESTING_make_trait_http_body_size (index, &rc->body_size), TALER_TESTING_trait_end (), }; if (! rc->done) return GNUNET_NO; return TALER_TESTING_get_trait (traits, ret, trait, index); } } /** * This function is used to start the web server. * * @param label command label * @param port is the port of the web server */ struct TALER_TESTING_Command TALER_TESTING_cmd_testserver (const char *label, uint16_t port) { struct TestserverState *ser; ser = GNUNET_new (struct TestserverState); ser->port = port; { struct TALER_TESTING_Command cmd = { .cls = ser, .label = label, .run = &testserver_run, .cleanup = &testserver_cleanup, .traits = &traits_testserver }; return cmd; } } /* end of testing_api_cmd_checkserver.c */