diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am index a61fc9dc..299c372d 100644 --- a/src/tests/client_server/Makefile.am +++ b/src/tests/client_server/Makefile.am @@ -25,7 +25,8 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile $(am__cd) $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la check_PROGRAMS = \ - test_client_server + test_client_server \ + test_postprocessor TESTS = $(check_PROGRAMS) @@ -47,3 +48,9 @@ test_client_server_SOURCES = \ test_client_server.c test_client_server_LDADD = \ libmhdt.la + + +test_postprocessor_SOURCES = \ + test_postprocessor.c +test_postprocessor_LDADD = \ + libmhdt.la diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h index 82abe857..1c00985a 100644 --- a/src/tests/client_server/libtest.h +++ b/src/tests/client_server/libtest.h @@ -144,6 +144,44 @@ MHDT_client_chunk_data (void *cls, const struct MHDT_PhaseContext *pc); +/** + * Arguments and state for the #MHDT_server_reply_check_post and + * #MHDT_client_do_post() functions. + */ +struct MHDT_PostInstructions +{ + /** + * Encoding to use when decoding. + */ + enum MHD_HTTP_PostEncoding enc; + + /** + * size to use for the buffer. + */ + size_t buffer_size; + + /** + * Size above which we switch to stream processing. + */ + size_t auto_stream_size; +}; + + +/** + * Perform POST request suitable for testing the + * post processor and expect a + * 204 No Content response. + * + * @param cls 0-terminated string with `struct MHDT_PostInstructions` + * @param pc context for the client + * @return error message, NULL on success + */ +const char * +MHDT_client_do_post ( + void *cls, + const struct MHDT_PhaseContext *pc); + + /** * A phase defines some server and client-side * behaviors to execute. @@ -386,6 +424,33 @@ MHDT_server_reply_check_upload ( uint_fast64_t upload_size); +/** + * Checks that the client request against the expected + * POST data. If so, returns #MHD_HTTP_STATUS_NO_CONTENT. + * + * @param cls expected upload data as a 0-terminated string. + * @param request the request object + * @param path the requested uri (without arguments after "?") + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param upload_size the size of the message upload content payload, + * #MHD_SIZE_UNKNOWN for chunked uploads (if the + * final chunk has not been processed yet) + * @return action how to proceed, NULL + * if the request must be aborted due to a serious + * error while handling the request (implies closure + * of underling data stream, for HTTP/1.1 it means + * socket closure). + */ +const struct MHD_Action * +MHDT_server_reply_check_post ( + void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size); + + /** * Initialize options for an MHD daemon for a test. * diff --git a/src/tests/client_server/libtest_convenience_client_request.c b/src/tests/client_server/libtest_convenience_client_request.c index 2cfa9250..c7f79d06 100644 --- a/src/tests/client_server/libtest_convenience_client_request.c +++ b/src/tests/client_server/libtest_convenience_client_request.c @@ -566,3 +566,60 @@ MHDT_client_chunk_data ( curl_easy_cleanup (c); return NULL; } + + +const char * +MHDT_client_do_post ( + void *cls, + const struct MHDT_PhaseContext *pc) +{ + struct MHDT_PostInstructions *pi = cls; + const char *text = "fixme"; + struct ReadBuffer rb = { + .buf = text, + .len = strlen (text), + .chunks = 2 + }; + CURL *c; + + c = curl_easy_init (); + if (NULL == c) + return "Failed to initialize Curl handle"; + if (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_URL, + pc->base_url)) + { + curl_easy_cleanup (c); + return "Failed to set URL for curl request"; + } + if (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_UPLOAD, + 1L)) + { + curl_easy_cleanup (c); + return "Failed to set PUT method for curl request"; + } + if (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_READFUNCTION, + &read_cb)) + { + curl_easy_cleanup (c); + return "Failed to set READFUNCTION for curl request"; + } + if (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_READDATA, + &rb)) + { + curl_easy_cleanup (c); + return "Failed to set READFUNCTION for curl request"; + } + PERFORM_REQUEST (c); + CHECK_STATUS (c, + MHD_HTTP_STATUS_NO_CONTENT); + curl_easy_cleanup (c); + return NULL; +} diff --git a/src/tests/client_server/libtest_convenience_server_reply.c b/src/tests/client_server/libtest_convenience_server_reply.c index 4ef6054d..d927c3f7 100644 --- a/src/tests/client_server/libtest_convenience_server_reply.c +++ b/src/tests/client_server/libtest_convenience_server_reply.c @@ -448,3 +448,84 @@ MHDT_server_reply_chunked_text ( cc, &free)); } + + +/** + * "Stream" reader for POST data. + * This callback is called to incrementally process parsed POST data sent by + * the client. + * + * @param cls user-specified closure + * @param name 0-terminated key for the value + * @param filename name of the uploaded file, NULL if not known + * @param content_type mime-type of the data, NULL if not known + * @param encoding the encoding of the data + * @param data pointer to @a size bytes of data at the + * specified @a off offset, + * NOT zero-terminated + * @param off offset of data in the overall value + * @param size number of bytes in @a data available + * @return action specifying how to proceed: + * #MHD_upload_action_continue() if all is well, + * #MHD_upload_action_suspend() to stop reading the upload until + * the request is resumed, + * #MHD_upload_action_abort_request() to close the socket, + * or a response to discard the rest of the upload and transmit + * the response + * @ingroup action + */ +static const struct MHD_UploadAction * +post_stream_reader (void *cls, + const struct MHD_String *name, + const struct MHD_StringNullable *filename, + const struct MHD_StringNullable *content_type, + const struct MHD_StringNullable *encoding, + const void *data, + uint_fast64_t off, + size_t size) +{ + struct MHDT_PostInstructions *pi = cls; + + return MHD_upload_action_continue (NULL); +} + + +/** + * The callback to be called when finished with processing + * of the postprocessor upload data. + * @param req the request + * @param cls the closure + * @return the action to proceed + */ +static const struct MHD_UploadAction * +post_stream_done (struct MHD_Request *req, + void *cls) +{ + struct MHDT_PostInstructions *pi = cls; + + return MHD_upload_action_from_response ( + req, + MHD_response_from_empty ( + MHD_HTTP_STATUS_NO_CONTENT)); +} + + +const struct MHD_Action * +MHDT_server_reply_check_post ( + void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + struct MHDT_PostInstructions *pi = cls; + + return MHD_action_parse_post (request, + pi->buffer_size, + pi->auto_stream_size, + pi->enc, + &post_stream_reader, + pi, + &post_stream_done, + pi); +} diff --git a/src/tests/client_server/test_postprocessor.c b/src/tests/client_server/test_postprocessor.c new file mode 100644 index 00000000..25d6a830 --- /dev/null +++ b/src/tests/client_server/test_postprocessor.c @@ -0,0 +1,102 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016, 2024 Christian Grothoff & Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file test_postprocessor.c + * @brief test with client against server + * @author Christian Grothoff + */ +#include +#include "mhd_config.h" +#include "libtest.h" + + +int +main (int argc, char *argv[]) +{ + struct MHD_DaemonOptionAndValue thread1auto[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct ServerType + { + const char *label; + MHDT_ServerSetup server_setup; + void *server_setup_cls; + MHDT_ServerRunner server_runner; + void *server_runner_cls; + } configs[] = { + { + .label = "auto-selected mode, single threaded", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread1auto, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "END" + } + }; + struct MHDT_PostInstructions simple_pi = { + .enc = MHD_HTTP_POST_ENCODING_FORM_URLENCODED, + .buffer_size = 32, + .auto_stream_size = 16 + }; + struct MHDT_Phase phases[] = { + { + .label = "simple post", + .server_cb = &MHDT_server_reply_check_post, + .server_cb_cls = &simple_pi, + .client_cb = &MHDT_client_do_post, + .client_cb_cls = &simple_pi, + .timeout_ms = 2500, + }, + { + .label = NULL, + }, + }; + unsigned int i; + + (void) argc; /* Unused. Silence compiler warning. */ + (void) argv; /* Unused. Silence compiler warning. */ + + for (i = 0; NULL != configs[i].server_setup; i++) + { + int ret; + + fprintf (stderr, + "Running tests with server setup `%s'\n", + configs[i].label); + ret = MHDT_test (configs[i].server_setup, + configs[i].server_setup_cls, + configs[i].server_runner, + configs[i].server_runner_cls, + phases); + if (0 != ret) + { + fprintf (stderr, + "Test failed with server of type `%s' (%u)\n", + configs[i].label, + i); + return ret; + } + } + return 0; +}