diff options
Diffstat (limited to 'src/testing/testing_api_cmd_testserver.c')
-rw-r--r-- | src/testing/testing_api_cmd_testserver.c | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/src/testing/testing_api_cmd_testserver.c b/src/testing/testing_api_cmd_testserver.c new file mode 100644 index 00000000..f47502a6 --- /dev/null +++ b/src/testing/testing_api_cmd_testserver.c @@ -0,0 +1,374 @@ +/* + 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 + <http://www.gnu.org/licenses/> +*/ + +/** + * @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 <gnunet/gnunet_curl_lib.h> +#include "taler/taler_testing_lib.h" +#include "taler/taler_mhd_lib.h" +#include "taler_merchant_testing_lib.h" +#include "taler_merchant_service.h" +#include <taler/taler_exchange_service.h> + +/** + * 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; i<ser->rcs_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 */ |