/*
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 */