commit e90465d04d6ef2d32a7ea0d920a2cb8cbfdc7d4a
parent c935fd0f80fb5758f3d28f42fbc5d3d70950e3bd
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 16 Apr 2026 11:06:08 +0200
refacotor httpd logic
Diffstat:
6 files changed, 1366 insertions(+), 1144 deletions(-)
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
@@ -0,0 +1,7 @@
+SUBDIRS = .
+
+paywallpkgdatadir = $(datadir)/paivana/
+paywallpkgdata_DATA = \
+ paywall.html
+
+EXTRA_DIST = $(paywallpkgdata_DATA)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -11,6 +11,7 @@ bin_PROGRAMS = \
paivana_httpd_SOURCES = \
paivana-httpd.c \
+ paivana-httpd_reverse.c paivana-httpd_reverse.h \
paivana_pd.c paivana_pd.h
paivana_httpd_LDADD = \
$(LIBGCRYPT_LIBS) \
diff --git a/src/backend/paivana-httpd.c b/src/backend/paivana-httpd.c
@@ -30,230 +30,27 @@
#include <curl/curl.h>
#include <gnunet/gnunet_util_lib.h>
#include <taler/taler_mhd_lib.h>
+#include "paivana-httpd.h"
+#include "paivana-httpd_reverse.h"
#include "paivana_pd.h"
-#define PAIVANA_LOG_INFO(...) \
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__)
-#define PAIVANA_LOG_DEBUG(...) \
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
-#define PAIVANA_LOG_WARNING(...) \
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__)
-#define PAIVANA_LOG_ERROR(...) \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__)
-
-#define REQUEST_BUFFER_MAX (1024 * 1024)
-#define UNIX_BACKLOG 500
-
-/**
- * Log curl error.
- *
- * @param level log level
- * @param fun name of curl_easy-function that gave the error
- * @param rc return code from curl
- */
-#define LOG_CURL_EASY(level,fun,rc) \
- GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \
- __LINE__, \
- curl_easy_strerror (rc))
-
-/* ******** Datastructures for HTTP handling ********** */
-
-
-/**
- * State machine for HTTP requests (per request).
- */
-enum RequestState
-{
- /**
- * Starting state.
- */
- REQUEST_STATE_WITH_MHD = 0,
-
- /**
- * We've started receiving upload data from MHD.
- */
- REQUEST_STATE_CLIENT_UPLOAD_STARTED,
-
- /**
- * Wa have started uploading data to the proxied service.
- */
- REQUEST_STATE_PROXY_UPLOAD_STARTED,
-
- /**
- * We're done with the upload from MHD.
- */
- REQUEST_STATE_CLIENT_UPLOAD_DONE,
-
- /**
- * We're done uploading data to the proxied service.
- */
- REQUEST_STATE_PROXY_UPLOAD_DONE,
-
- /**
- * We've finished uploading data via CURL and can now download.
- */
- REQUEST_STATE_PROXY_DOWNLOAD_STARTED,
-
- /**
- * We've finished receiving download data from cURL.
- */
- REQUEST_STATE_PROXY_DOWNLOAD_DONE
-};
-
-
-/**
- * A header list
- */
-struct HttpResponseHeader
-{
- /**
- * DLL
- */
- struct HttpResponseHeader *next;
-
- /**
- * DLL
- */
- struct HttpResponseHeader *prev;
-
- /**
- * Header type
- */
- char *type;
-
- /**
- * Header value
- */
- char *value;
-};
+/* *********************** Globals **************************** */
-/**
- * A structure for socks requests
- */
-struct HttpRequest
+struct RequestContext
{
-
- /**
- * Kept in DLL.
- */
- struct HttpRequest *prev;
-
- /**
- * Kept in DLL.
- */
- struct HttpRequest *next;
-
- /**
- * MHD request that triggered us.
- */
- struct MHD_Connection *con;
-
- /**
- * Client socket read task
- */
- struct GNUNET_SCHEDULER_Task *rtask;
-
- /**
- * Client socket write task
- */
- struct GNUNET_SCHEDULER_Task *wtask;
-
- /**
- * Hold the response obtained by modifying the original one.
- */
- struct MHD_Response *mod_response;
-
- /**
- * MHD response object for this request.
- */
- struct MHD_Response *response;
-
- /**
- * The URL to fetch
- */
- char *url;
-
- /**
- * Handle to cURL
- */
- CURL *curl;
-
- /**
- * HTTP request headers for the curl request.
- */
- struct curl_slist *headers;
-
- /**
- * Headers from response
- */
- struct HttpResponseHeader *header_head;
-
- /**
- * Headers from response
- */
- struct HttpResponseHeader *header_tail;
-
- /**
- * Buffer we use for moving data between MHD and
- * curl (in both directions).
- */
- char *io_buf;
-
- /**
- * Number of bytes already in the IO buffer.
- */
- size_t io_len;
-
/**
- * Number of bytes allocated for the IO buffer.
+ * Handle for request forwarding as reverse proxy.
*/
- unsigned int io_size;
-
- /**
- * HTTP response code to give to MHD for the response.
- */
- unsigned int response_code;
-
- /**
- * Request processing state machine.
- */
- enum RequestState state;
-
- /**
- * Did we suspend MHD processing?
- */
- enum GNUNET_GenericReturnValue suspended;
+ struct HttpRequest *hr;
/**
- * Did we pause CURL processing?
+ * We are past the paywall, forward to client.
*/
- int curl_paused;
+ bool do_forward;
};
-/* *********************** Globals **************************** */
-
-/**
- * The cURL download task (curl multi API).
- */
-static struct GNUNET_SCHEDULER_Task *curl_download_task;
-
-/**
- * DLL of active HTTP requests.
- */
-static struct HttpRequest *hr_head;
-
-/**
- * DLL of active HTTP requests.
- */
-static struct HttpRequest *hr_tail;
-
-/**
- * The cURL multi handle
- */
-static CURLM *curl_multi;
-
/**
* Set to true if we started a daemon.
*/
@@ -265,11 +62,6 @@ static bool have_daemons;
static struct MHD_Response *paywall;
/**
- * Response we return on cURL failures.
- */
-static struct MHD_Response *curl_failure_response;
-
-/**
* Our configuration.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
@@ -284,11 +76,7 @@ static int no_check;
*/
static int global_ret;
-/**
- * Destination to which HTTP server we forward requests to.
- * Of the format "http://servername:PORT"
- */
-static char *target_server_base_url;
+char *target_server_base_url;
/**
* Merchant backend base URL.
@@ -306,7 +94,7 @@ static char *merchant_access_token;
static struct GNUNET_HashCode paivana_secret;
-/* ********************* Cookie handling ****************** */
+/* ********************* Paivana Cookie handling ****************** */
/**
* Compute access cookie hash for the given @a expiration and @a ca.
@@ -422,576 +210,6 @@ compute_cookie (struct GNUNET_TIME_Timestamp expiration,
}
-/* ********************* Global helpers ****************** */
-
-/**
- * Run MHD now, we have extra data ready for the callback.
- */
-static void
-run_mhd_now (void);
-
-
-/* *************** HTTP handling with cURL ***************** */
-
-
-/**
- * Transform _one_ CURL header (gotten from the request) into
- * MHD format and put it into the response headers list; mostly
- * copies the headers, but makes special adjustments based on
- * control requests.
- *
- * @param buffer curl buffer with a single
- * line of header data; not 0-terminated!
- * @param size curl blocksize
- * @param nmemb curl blocknumber
- * @param cls our `struct HttpRequest *`
- * @return size of processed bytes
- */
-static size_t
-curl_check_hdr (void *buffer,
- size_t size,
- size_t nmemb,
- void *cls)
-{
- struct HttpRequest *hr = cls;
- struct HttpResponseHeader *header;
- size_t bytes = size * nmemb;
- char *ndup;
- const char *hdr_type;
- char *hdr_val;
- char *tok;
-
- /* Raw line is not guaranteed to be null-terminated. */
- ndup = GNUNET_malloc (bytes + 1);
- memcpy (ndup,
- buffer,
- bytes);
- ndup[bytes] = '\0';
- hdr_type = strtok (ndup, ":");
- if (NULL == hdr_type)
- {
- GNUNET_free (ndup);
- return bytes;
- }
- hdr_val = strtok (NULL, "");
- if (NULL == hdr_val)
- {
- GNUNET_free (ndup);
- return bytes;
- }
- if (' ' == *hdr_val)
- hdr_val++;
-
- /* MHD does not allow certain characters in values,
- * remove those, plus those could alter strings matching. */
- if (NULL != (tok = strchr (hdr_val, '\n')))
- *tok = '\0';
- if (NULL != (tok = strchr (hdr_val, '\r')))
- *tok = '\0';
- if (NULL != (tok = strchr (hdr_val, '\t')))
- *tok = '\0';
- PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n",
- hdr_type,
- hdr_val);
- /* Skip "Content-length:" header as it will be wrong, given
- that we are man-in-the-middling the connection */
- if (0 == strcasecmp (hdr_type,
- MHD_HTTP_HEADER_CONTENT_LENGTH))
- {
- GNUNET_free (ndup);
- return bytes;
- }
- /* Skip "Connection: Keep-Alive" header, it will be
- done by MHD if possible */
- if ( (0 == strcasecmp (hdr_type,
- MHD_HTTP_HEADER_CONNECTION)) &&
- (0 == strcasecmp (hdr_val,
- "Keep-Alive")) )
- {
- GNUNET_free (ndup);
- return bytes;
- }
- if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
- {
- header = GNUNET_new (struct HttpResponseHeader);
- header->type = GNUNET_strdup (hdr_type);
- header->value = GNUNET_strdup (hdr_val);
- GNUNET_CONTAINER_DLL_insert (hr->header_head,
- hr->header_tail,
- header);
- }
- GNUNET_free (ndup);
- return bytes;
-}
-
-
-/**
- * Create the MHD response with CURL's as starting base;
- * mainly set the response code and parses the response into
- * JSON, if it is such.
- *
- * @param hr pointer to where to store the new data. Despite
- * its name, the struct contains response data as well.
- * @return #GNUNET_OK if it succeeds.
- */
-static enum GNUNET_GenericReturnValue
-create_mhd_response_from_hr (struct HttpRequest *hr)
-{
- long resp_code;
-
- if (NULL != hr->response)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Response already set!\n");
- return GNUNET_SYSERR;
- }
- GNUNET_break (CURLE_OK ==
- curl_easy_getinfo (hr->curl,
- CURLINFO_RESPONSE_CODE,
- &resp_code));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating MHD response with code %u\n",
- (unsigned int) resp_code);
- hr->response_code = resp_code;
- if (GNUNET_YES == hr->suspended)
- {
- MHD_resume_connection (hr->con);
- hr->suspended = GNUNET_NO;
- }
- run_mhd_now ();
- return GNUNET_OK;
-}
-
-
-/**
- * Handle response payload data from cURL.
- * Copies it into our `io_buf` to make it available to MHD.
- *
- * @param ptr pointer to the data
- * @param size number of blocks of data
- * @param nmemb blocksize
- * @param ctx our `struct HttpRequest *`
- * @return number of bytes handled
- */
-static size_t
-curl_download_cb (void *ptr,
- size_t size,
- size_t nmemb,
- void *ctx)
-{
- struct HttpRequest *hr = ctx;
- size_t total = size * nmemb;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Curl download proceeding\n");
-
- if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state)
- {
- /* Web server started with response before we finished
- the upload. In this case, current libcurl decides
- to NOT complete the upload, so we should jump in the
- state machine to process the download, dropping the
- rest of the upload. This should only really happen
- with uploads without "Expect: 100 Continue" and
- Web servers responding with an error (i.e. upload
- not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
- GNUNET_log
- (GNUNET_ERROR_TYPE_INFO,
- "Stopping %u byte upload: we are already downloading...\n",
- (unsigned int) hr->io_len);
- hr->io_len = 0;
- }
-
- if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Download callback goes to sleep\n");
- hr->curl_paused = GNUNET_YES;
- return CURL_WRITEFUNC_PAUSE;
- }
- GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED ==
- hr->state);
- if (hr->io_size - hr->io_len < total)
- {
- GNUNET_assert (total + hr->io_size >= total);
- GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
- GNUNET_array_grow (hr->io_buf,
- hr->io_size,
- GNUNET_MAX (total + hr->io_len,
- hr->io_size * 2 + 1024));
- }
- GNUNET_memcpy (&hr->io_buf[hr->io_len],
- ptr,
- total);
- hr->io_len += total;
- return total;
-}
-
-
-/**
- * Ask cURL for the select() sets and schedule cURL operations.
- */
-static void
-curl_download_prepare (void);
-
-
-/**
- * cURL callback for uploaded (PUT/POST) data.
- * Copies from our `io_buf` to make it available to cURL.
- *
- * @param buf where to write the data
- * @param size number of bytes per member
- * @param nmemb number of members available in @a buf
- * @param cls our `struct HttpRequest` that generated the data
- * @return number of bytes copied to @a buf
- */
-static size_t
-curl_upload_cb (void *buf,
- size_t size,
- size_t nmemb,
- void *cls)
-{
- struct HttpRequest *hr = cls;
- size_t len = size * nmemb;
- size_t to_copy;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Upload cb is working...\n");
-
- if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
- (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) )
- {
- GNUNET_log
- (GNUNET_ERROR_TYPE_INFO,
- "Upload cb aborts: we are already downloading...\n");
- return CURL_READFUNC_ABORT;
- }
-
- if ( (0 == hr->io_len) &&
- (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Pausing CURL UPLOAD, need more data\n");
- return CURL_READFUNC_PAUSE;
- }
-
- /**
- * We got rescheduled because the download callback was asleep.
- * FIXME: can this block be eliminated and the unpausing being
- * moved in the last block where we return zero as well?
- */
- if ( (0 == hr->io_len) &&
- (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) )
- {
- if (GNUNET_YES == hr->curl_paused)
- {
- hr->curl_paused = GNUNET_NO;
- curl_easy_pause (hr->curl,
- CURLPAUSE_CONT);
- }
- curl_download_prepare ();
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Completed CURL UPLOAD\n");
- return 0; /* upload finished, can now download */
- }
- to_copy = GNUNET_MIN (hr->io_len,
- len);
- GNUNET_memcpy (buf,
- hr->io_buf,
- to_copy);
- /* shift remaining data back to the beginning of the buffer. */
- memmove (hr->io_buf,
- &hr->io_buf[to_copy],
- hr->io_len - to_copy);
- hr->io_len -= to_copy;
- if (0 == hr->io_len)
- {
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Completed CURL UPLOAD\n");
- }
- return to_copy;
-}
-
-
-/* ************** helper functions ************* */
-
-/**
- * Extract the hostname from a complete URL.
- *
- * @param url full fledged URL
- * @return pointer to the 0-terminated hostname, to be freed
- * by the caller.
- */
-static char *
-build_host_header (const char *url)
-{
- #define MARKER "://"
-
- char *header;
- char *end;
- char *hostname;
- char *dup = GNUNET_strdup (url);
-
- hostname = strstr (dup,
- MARKER);
- hostname += 3;
- end = strchrnul (hostname, '/');
- *end = '\0';
- GNUNET_asprintf (&header,
- "Host: %s",
- hostname);
- GNUNET_free (dup);
- return header;
-}
-
-
-/* ************** main loop of cURL interaction ************* */
-
-
-/**
- * Task that is run when we are ready to receive more data
- * from curl
- *
- * @param cls closure
- */
-static void
-curl_task_download (void *cls);
-
-
-/**
- * Ask cURL for the select() sets and schedule cURL operations.
- */
-static void
-curl_download_prepare ()
-{
- CURLMcode mret;
- fd_set rs;
- fd_set ws;
- fd_set es;
- int max;
- struct GNUNET_NETWORK_FDSet *grs;
- struct GNUNET_NETWORK_FDSet *gws;
- long to;
- struct GNUNET_TIME_Relative rtime;
-
- if (NULL != curl_download_task)
- {
- GNUNET_SCHEDULER_cancel (curl_download_task);
- curl_download_task = NULL;
- }
- max = -1;
- FD_ZERO (&rs);
- FD_ZERO (&ws);
- FD_ZERO (&es);
- if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
- &rs,
- &ws,
- &es,
- &max)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "%s failed at %s:%d: `%s'\n",
- "curl_multi_fdset",
- __FILE__,
- __LINE__,
- curl_multi_strerror (mret));
- return;
- }
- to = -1;
- GNUNET_break (CURLM_OK ==
- curl_multi_timeout (curl_multi,
- &to));
- if (-1 == to)
- rtime = GNUNET_TIME_UNIT_FOREVER_REL;
- else
- rtime = GNUNET_TIME_relative_multiply
- (GNUNET_TIME_UNIT_MILLISECONDS, to);
- if (-1 != max)
- {
- grs = GNUNET_NETWORK_fdset_create ();
- gws = GNUNET_NETWORK_fdset_create ();
- GNUNET_NETWORK_fdset_copy_native (grs,
- &rs,
- max + 1);
- GNUNET_NETWORK_fdset_copy_native (gws,
- &ws,
- max + 1);
- curl_download_task
- = GNUNET_SCHEDULER_add_select (
- GNUNET_SCHEDULER_PRIORITY_DEFAULT,
- rtime,
- grs, gws,
- &curl_task_download,
- curl_multi);
- GNUNET_NETWORK_fdset_destroy (gws);
- GNUNET_NETWORK_fdset_destroy (grs);
- }
- else
- {
- curl_download_task = GNUNET_SCHEDULER_add_delayed
- (rtime,
- &curl_task_download,
- curl_multi);
- }
-}
-
-
-/**
- * "Filter" function that translates MHD request headers to
- * cURL's.
- *
- * @param cls our `struct HttpRequest`
- * @param kind value kind
- * @param key field key
- * @param value field value
- * @return #MHD_YES to continue to iterate
- */
-static enum MHD_Result
-con_val_iter (void *cls,
- enum MHD_ValueKind kind,
- const char *key,
- const char *value)
-{
- struct HttpRequest *hr = cls;
- char *hdr;
- char *new_value = NULL;
-
- (void) kind;
- if (0 == strcmp (MHD_HTTP_HEADER_HOST,
- key))
- {
- /* We don't take the host header as given in the request.
- * We'll instead put the proxied service's hostname in it*/
- return MHD_YES;
- }
- if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
- key)))
- {
- PAIVANA_LOG_INFO (
- "Do not set Content-Length for request\n");
- return MHD_YES;
- }
- GNUNET_asprintf (&hdr,
- "%s: %s",
- key,
- value);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Adding header `%s' to HTTP request\n",
- hdr);
- hr->headers = curl_slist_append (hr->headers,
- hdr);
- GNUNET_free (hdr);
- GNUNET_free (new_value);
- return MHD_YES;
-}
-
-
-/**
- * Task that is run when we are ready to receive
- * more data from curl.
- *
- * @param cls closure, usually NULL.
- */
-static void
-curl_task_download (void *cls)
-{
- int running;
- int msgnum;
- struct CURLMsg *msg;
- CURLMcode mret;
- struct HttpRequest *hr;
-
- (void) cls;
- curl_download_task = NULL;
- do
- {
- running = 0;
- mret = curl_multi_perform (curl_multi,
- &running);
- while (NULL != (msg = curl_multi_info_read (curl_multi,
- &msgnum)))
- {
- GNUNET_break
- (CURLE_OK == curl_easy_getinfo
- (msg->easy_handle,
- CURLINFO_PRIVATE,
- (char **) &hr));
-
- if (NULL == hr)
- {
- GNUNET_break (0);
- continue;
- }
- switch (msg->msg)
- {
- case CURLMSG_NONE:
- /* documentation says this is not used */
- GNUNET_break (0);
- break;
- case CURLMSG_DONE:
- switch (msg->data.result)
- {
- case CURLE_OK:
- case CURLE_GOT_NOTHING:
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "CURL download completed.\n");
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
- if (NULL == hr->response)
- GNUNET_assert (GNUNET_OK ==
- create_mhd_response_from_hr (hr));
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Download curl failed: %s\n",
- curl_easy_strerror (msg->data.result));
- /* FIXME: indicate error somehow?
- * close MHD connection badly as well? */
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
- if (GNUNET_YES == hr->suspended)
- {
- MHD_resume_connection (hr->con);
- hr->suspended = GNUNET_NO;
- }
- run_mhd_now ();
- break;
- }
- if (NULL == hr->response)
- hr->response = curl_failure_response;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Curl request for `%s' finished (got the response)\n",
- hr->url);
- run_mhd_now ();
- break;
- case CURLMSG_LAST:
- /* documentation says this is not used */
- GNUNET_break (0);
- break;
- default:
- /* unexpected status code */
- GNUNET_break (0);
- break;
- }
- }
- } while (mret == CURLM_CALL_MULTI_PERFORM);
- if (CURLM_OK != mret)
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "%s failed at %s:%d: `%s'\n",
- "curl_multi_perform",
- __FILE__,
- __LINE__,
- curl_multi_strerror (mret));
- if (0 == running)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending cURL multi loop,"
- " no more events pending\n");
- return; /* nothing more in progress */
- }
- curl_download_prepare ();
-}
-
-
/* *************** MHD response generation ***************** */
@@ -1029,20 +247,18 @@ create_response (void *cls,
size_t *upload_data_size,
void **con_cls)
{
- struct HttpRequest *hr = *con_cls;
+ struct RequestContext *rc = *con_cls;
+ struct HttpRequest *hr = rc->hr;
(void) cls;
- // FIXME: check if url is one that we reverse proxy!
-
- (void) url;
-
if (NULL == hr)
{
GNUNET_break (0);
return MHD_NO;
}
+ // FIXME: check if url is one that we reverse proxy!
- if (REQUEST_STATE_WITH_MHD == hr->state)
+ if (! rc->do_forward)
{
const char *cookie;
bool ok = (0 != no_check);
@@ -1087,258 +303,18 @@ create_response (void *cls,
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Request ok!\n");
- hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED;
- /* TODO: hacks for 100 continue suppression would go here! */
- return MHD_YES;
- }
-
- // FIXME: move vanilla reverse proxy logic to another file!
-
- /* continuing to process request */
- if (0 != *upload_data_size)
- {
- GNUNET_assert
- (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state);
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Processing %u bytes UPLOAD\n",
- (unsigned int) *upload_data_size);
-
- /* Grow the buffer if remaining space isn't enough. */
- if (hr->io_size - hr->io_len < *upload_data_size)
- {
- /* How can this assertion be false? */
- GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
- /* This asserts that upload_data_size > 0, ? */
- GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len);
-
- GNUNET_array_grow (hr->io_buf,
- hr->io_size,
- GNUNET_MAX
- (hr->io_size * 2 + 1024,
- *upload_data_size + hr->io_len));
- }
-
- /* Finally copy upload data. */
- GNUNET_memcpy (&hr->io_buf[hr->io_len],
- upload_data,
- *upload_data_size);
-
- hr->io_len += *upload_data_size;
- *upload_data_size = 0;
-
+ rc->do_forward = true;
+ /* TODO: hacks for 100 continue suppression should go here! */
return MHD_YES;
}
- /* Upload (*from the client*) finished or just a without-body
- * request. */
- if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state)
- {
- hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Finished processing UPLOAD\n");
- }
-
- /* generate curl request to the proxied service. */
- if (NULL == hr->curl)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Generating curl request\n");
- hr->curl = curl_easy_init ();
- if (NULL == hr->curl)
- {
- PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
- return MHD_queue_response (con,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- curl_failure_response);
- }
-
- /* No need to check whether we're POSTing or PUTting.
- * If not needed, one of the following values will be
- * ignored.*/
- curl_easy_setopt (hr->curl,
- CURLOPT_POSTFIELDSIZE,
- hr->io_len);
- curl_easy_setopt (hr->curl,
- CURLOPT_INFILESIZE,
- hr->io_len);
- curl_easy_setopt (hr->curl,
- CURLOPT_HEADERFUNCTION,
- &curl_check_hdr);
- curl_easy_setopt (hr->curl,
- CURLOPT_HEADERDATA,
- hr);
- curl_easy_setopt (hr->curl,
- CURLOPT_FOLLOWLOCATION,
- 0);
- curl_easy_setopt (hr->curl,
- CURLOPT_CONNECTTIMEOUT,
- 60L);
- curl_easy_setopt (hr->curl,
- CURLOPT_TIMEOUT,
- 60L);
- curl_easy_setopt (hr->curl,
- CURLOPT_NOSIGNAL,
- 1L);
- curl_easy_setopt (hr->curl,
- CURLOPT_PRIVATE,
- hr);
- curl_easy_setopt (hr->curl,
- CURLOPT_VERBOSE,
- 0);
-
- curl_easy_setopt (hr->curl,
- CURLOPT_READFUNCTION,
- &curl_upload_cb);
- curl_easy_setopt (hr->curl,
- CURLOPT_READDATA,
- hr);
-
- curl_easy_setopt (hr->curl,
- CURLOPT_WRITEFUNCTION,
- &curl_download_cb);
- curl_easy_setopt (hr->curl,
- CURLOPT_WRITEDATA,
- hr);
- {
- char *curlurl;
- char *host_hdr;
-
- GNUNET_asprintf (&curlurl,
- "%s%s",
- target_server_base_url,
- hr->url);
- curl_easy_setopt (hr->curl,
- CURLOPT_URL,
- curlurl);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Forwarding request to: %s\n",
- curlurl);
- GNUNET_free (curlurl);
-
- host_hdr = build_host_header (target_server_base_url);
- PAIVANA_LOG_DEBUG ("Faking the host header, %s\n",
- host_hdr);
- hr->headers = curl_slist_append (hr->headers,
- host_hdr);
- GNUNET_free (host_hdr);
- }
-
- // FIXME: support PATCH, etc.
- if (0 == strcasecmp (meth,
- MHD_HTTP_METHOD_PUT))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Crafting a CURL PUT request\n");
-
- curl_easy_setopt (hr->curl,
- CURLOPT_UPLOAD,
- 1L);
- hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
- }
- else if (0 == strcasecmp (meth,
- MHD_HTTP_METHOD_POST))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Crafting a CURL POST request\n");
- curl_easy_setopt (hr->curl,
- CURLOPT_POST,
- 1L);
- curl_easy_setopt (hr->curl,
- CURLOPT_VERBOSE,
- 1L);
- hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
- }
- else if (0 == strcasecmp (meth,
- MHD_HTTP_METHOD_HEAD))
- {
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
- curl_easy_setopt (hr->curl,
- CURLOPT_NOBODY,
- 1L);
- }
- else if (0 == strcasecmp (meth,
- MHD_HTTP_METHOD_OPTIONS))
- {
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
- curl_easy_setopt (hr->curl,
- CURLOPT_CUSTOMREQUEST,
- "OPTIONS");
- }
- else if (0 == strcasecmp (meth,
- MHD_HTTP_METHOD_GET))
- {
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
- curl_easy_setopt (hr->curl,
- CURLOPT_HTTPGET,
- 1L);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unsupported HTTP method `%s'\n",
- meth);
- curl_easy_cleanup (hr->curl);
- hr->curl = NULL;
- return MHD_NO;
- }
-
- if (CURLM_OK !=
- curl_multi_add_handle (curl_multi,
- hr->curl))
- {
- GNUNET_break (0);
- curl_easy_cleanup (hr->curl);
- hr->curl = NULL;
- return MHD_NO;
- }
-
- MHD_get_connection_values (con,
- MHD_HEADER_KIND,
- &con_val_iter,
- hr);
-
- curl_easy_setopt (hr->curl,
- CURLOPT_HTTPHEADER,
- hr->headers);
- curl_download_prepare ();
-
- return MHD_YES;
- }
-
- if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state)
- {
- GNUNET_assert (GNUNET_NO == hr->suspended);
- MHD_suspend_connection (con);
- hr->suspended = GNUNET_YES;
- return MHD_YES; /* wait for curl */
- }
-
- GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state);
-
- hr->response
- = MHD_create_response_from_buffer_copy (hr->io_len,
- hr->io_buf);
- for (struct HttpResponseHeader *header = hr->header_head;
- NULL != header;
- header = header->next)
- {
- const char *value = header->value;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Adding MHD response header %s->%s\n",
- header->type,
- value);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (hr->response,
- header->type,
- value));
- }
- run_mhd_now ();
-
- return MHD_queue_response (con,
- hr->response_code,
- hr->response);
+ return PAIVANA_HTTPD_reverse (hr,
+ con,
+ url,
+ meth,
+ ver,
+ upload_data,
+ upload_data_size);
}
@@ -1353,7 +329,7 @@ create_response (void *cls,
* @param connection connection handle
* @param con_cls value as set by the last call to
* the MHD_AccessHandlerCallback, should be
- * our `struct HttpRequest *` (set by `create_response()`)
+ * our `struct RequestContext *` (created in `mhd_log_callback()`)
* @param toe reason for request termination (ignored)
*/
static void
@@ -1362,59 +338,18 @@ mhd_completed_cb (void *cls,
void **con_cls,
enum MHD_RequestTerminationCode toe)
{
- struct HttpRequest *hr = *con_cls;
- struct HttpResponseHeader *header;
+ struct RequestContext *rc = *con_cls;
(void) cls;
(void) connection;
- if (NULL == hr)
+ if (NULL == rc)
return;
if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"MHD encountered error handling request: %d\n",
toe);
- if (NULL != hr->curl)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Resetting cURL handle\n");
- curl_multi_remove_handle (curl_multi,
- hr->curl);
- curl_easy_cleanup (hr->curl);
- hr->curl = NULL;
- hr->io_len = 0;
- }
- if (NULL != hr->headers)
- {
- curl_slist_free_all (hr->headers);
- hr->headers = NULL;
- }
- if ( (NULL != hr->response) &&
- (curl_failure_response != hr->response) )
- /* Destroy non-error responses... (?) */
- MHD_destroy_response (hr->response);
-
- for (header = hr->header_head;
- header != NULL;
- header = hr->header_head)
- {
- GNUNET_CONTAINER_DLL_remove (hr->header_head,
- hr->header_tail,
- header);
- GNUNET_free (header->type);
- GNUNET_free (header->value);
- GNUNET_free (header);
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Proxying of '%s' completely done\n",
- hr->url);
-
- GNUNET_free (hr->url);
- GNUNET_free (hr->io_buf);
- GNUNET_CONTAINER_DLL_remove (hr_head,
- hr_tail,
- hr);
- GNUNET_free (hr);
+ PAIVANA_HTTPD_reverse_cleanup (rc->hr);
+ GNUNET_free (rc);
*con_cls = NULL;
}
@@ -1430,14 +365,14 @@ mhd_completed_cb (void *cls,
* @param cls the HTTP server handle (a `struct MhdHttpList`)
* @param url the URL that is being requested
* @param connection MHD connection object for the request
- * @return the `struct HttpRequest` that this @a connection is for
+ * @return the `struct RequestContext` that this @a connection is for
*/
static void *
mhd_log_callback (void *cls,
const char *url,
struct MHD_Connection *connection)
{
- struct HttpRequest *hr;
+ struct RequestContext *rc;
const union MHD_ConnectionInfo *ci;
(void) cls;
@@ -1451,24 +386,10 @@ mhd_log_callback (void *cls,
GNUNET_break (0);
return NULL;
}
-
- hr = GNUNET_new (struct HttpRequest);
- hr->con = connection;
- hr->url = GNUNET_strdup (url);
- GNUNET_CONTAINER_DLL_insert (hr_head,
- hr_tail,
- hr);
- return hr;
-}
-
-
-/**
- * Run MHD now, we have extra data ready for the callback.
- */
-static void
-run_mhd_now (void)
-{
- TALER_MHD_daemon_trigger ();
+ rc = GNUNET_new (struct RequestContext);
+ rc->hr = PAIVANA_HTTPD_reverse_create (connection,
+ url);
+ return rc;
}
@@ -1487,28 +408,8 @@ do_shutdown (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Shutting down...\n");
TALER_MHD_daemons_halt ();
- /* MHD requires resuming before destroying the daemons */
- for (struct HttpRequest *hr = hr_head;
- NULL != hr;
- hr = hr->next)
- {
- if (GNUNET_YES == hr->suspended)
- {
- hr->suspended = GNUNET_NO;
- MHD_resume_connection (hr->con);
- }
- }
+ PAIVANA_HTTPD_reverse_shutdown ();
TALER_MHD_daemons_destroy ();
- if (NULL != curl_multi)
- {
- curl_multi_cleanup (curl_multi);
- curl_multi = NULL;
- }
- if (NULL != curl_download_task)
- {
- GNUNET_SCHEDULER_cancel (curl_download_task);
- curl_download_task = NULL;
- }
GNUNET_free (target_server_base_url);
}
@@ -1628,22 +529,15 @@ run (void *cls,
(void) args;
(void) cfgfile;
cfg = c;
- if (0 != curl_global_init (CURL_GLOBAL_WIN32))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "cURL global init failed!\n");
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
+
if (! load_paywall ())
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (NULL == (curl_multi = curl_multi_init ()))
+ if (! PAIVANA_HTTPD_reverse_init ())
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to create cURL multi handle!\n");
+ GNUNET_SCHEDULER_shutdown ();
return;
}
diff --git a/src/backend/paivana-httpd.h b/src/backend/paivana-httpd.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2026 Taler Systems SA
+
+ Paivana 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.
+
+ Paivana 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 Paivana; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 51 Franklin
+ Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+/**
+ * @author Christian Grothoff
+ * @file paivana-httpd.h
+ *
+ * @brief
+ */
+#ifndef PAIVANA_HTTPD_H
+#define PAIVANA_HTTPD_H
+
+#define PAIVANA_LOG_INFO(...) \
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__)
+#define PAIVANA_LOG_DEBUG(...) \
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
+#define PAIVANA_LOG_WARNING(...) \
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__)
+#define PAIVANA_LOG_ERROR(...) \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__)
+
+/**
+ * Destination to which HTTP server we forward requests to.
+ * Of the format "http://servername:PORT"
+ */
+extern char *target_server_base_url;
+
+
+#endif
diff --git a/src/backend/paivana-httpd_reverse.c b/src/backend/paivana-httpd_reverse.c
@@ -0,0 +1,1193 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2012-2014 GNUnet e.V.
+ Copyright (C) 2018, 2025, 2026 Taler Systems SA
+
+ GNU 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.
+
+ GNU 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 GNU Taler; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 51 Franklin
+ Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+/**
+ * @author Martin Schanzenbach
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ * @file src/backend/paivana-httpd_reverse.c
+ * @brief Reverse proxy logic that just forwards the request
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_mhd_lib.h>
+#include "paivana-httpd_reverse.h"
+
+#define REQUEST_BUFFER_MAX (1024 * 1024)
+
+
+/**
+ * Log curl error.
+ *
+ * @param level log level
+ * @param fun name of curl_easy-function that gave the error
+ * @param rc return code from curl
+ */
+#define LOG_CURL_EASY(level,fun,rc) \
+ GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \
+ __LINE__, \
+ curl_easy_strerror (rc))
+
+
+/**
+ * State machine for HTTP requests (per request).
+ */
+enum RequestState
+{
+ /**
+ * We've started receiving upload data from MHD.
+ * Initial state.
+ */
+ REQUEST_STATE_CLIENT_UPLOAD_STARTED,
+
+ /**
+ * Wa have started uploading data to the proxied service.
+ */
+ REQUEST_STATE_PROXY_UPLOAD_STARTED,
+
+ /**
+ * We're done with the upload from MHD.
+ */
+ REQUEST_STATE_CLIENT_UPLOAD_DONE,
+
+ /**
+ * We're done uploading data to the proxied service.
+ */
+ REQUEST_STATE_PROXY_UPLOAD_DONE,
+
+ /**
+ * We've finished uploading data via CURL and can now download.
+ */
+ REQUEST_STATE_PROXY_DOWNLOAD_STARTED,
+
+ /**
+ * We've finished receiving download data from cURL.
+ */
+ REQUEST_STATE_PROXY_DOWNLOAD_DONE
+};
+
+
+/**
+ * A header list
+ */
+struct HttpResponseHeader
+{
+ /**
+ * DLL
+ */
+ struct HttpResponseHeader *next;
+
+ /**
+ * DLL
+ */
+ struct HttpResponseHeader *prev;
+
+ /**
+ * Header type
+ */
+ char *type;
+
+ /**
+ * Header value
+ */
+ char *value;
+};
+
+
+/**
+ * A structure for socks requests
+ */
+struct HttpRequest
+{
+
+ /**
+ * Kept in DLL.
+ */
+ struct HttpRequest *prev;
+
+ /**
+ * Kept in DLL.
+ */
+ struct HttpRequest *next;
+
+ /**
+ * MHD request that triggered us.
+ */
+ struct MHD_Connection *con;
+
+ /**
+ * Client socket read task
+ */
+ struct GNUNET_SCHEDULER_Task *rtask;
+
+ /**
+ * Client socket write task
+ */
+ struct GNUNET_SCHEDULER_Task *wtask;
+
+ /**
+ * Hold the response obtained by modifying the original one.
+ */
+ struct MHD_Response *mod_response;
+
+ /**
+ * MHD response object for this request.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * The URL to fetch
+ */
+ char *url;
+
+ /**
+ * Handle to cURL
+ */
+ CURL *curl;
+
+ /**
+ * HTTP request headers for the curl request.
+ */
+ struct curl_slist *headers;
+
+ /**
+ * Headers from response
+ */
+ struct HttpResponseHeader *header_head;
+
+ /**
+ * Headers from response
+ */
+ struct HttpResponseHeader *header_tail;
+
+ /**
+ * Buffer we use for moving data between MHD and
+ * curl (in both directions).
+ */
+ char *io_buf;
+
+ /**
+ * Number of bytes already in the IO buffer.
+ */
+ size_t io_len;
+
+ /**
+ * Number of bytes allocated for the IO buffer.
+ */
+ unsigned int io_size;
+
+ /**
+ * HTTP response code to give to MHD for the response.
+ */
+ unsigned int response_code;
+
+ /**
+ * Request processing state machine.
+ */
+ enum RequestState state;
+
+ /**
+ * Did we suspend MHD processing?
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+ /**
+ * Did we pause CURL processing?
+ */
+ int curl_paused;
+};
+
+
+/**
+ * DLL of active HTTP requests.
+ */
+static struct HttpRequest *hr_head;
+
+/**
+ * DLL of active HTTP requests.
+ */
+static struct HttpRequest *hr_tail;
+
+/**
+ * Response we return on cURL failures.
+ */
+static struct MHD_Response *curl_failure_response;
+
+/**
+ * The cURL multi handle
+ */
+static CURLM *curl_multi;
+
+/**
+ * The cURL download task (curl multi API).
+ */
+static struct GNUNET_SCHEDULER_Task *curl_download_task;
+
+
+bool
+PAIVANA_HTTPD_reverse_init (void)
+{
+ if (0 != curl_global_init (CURL_GLOBAL_WIN32))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "cURL global init failed!\n");
+ return false;
+ }
+ if (NULL == (curl_multi = curl_multi_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create cURL multi handle!\n");
+ return false;
+ }
+ return true;
+}
+
+
+void
+PAIVANA_HTTPD_reverse_shutdown (void)
+{
+ for (struct HttpRequest *hr = hr_head;
+ NULL != hr;
+ hr = hr->next)
+ {
+ if (GNUNET_YES == hr->suspended)
+ {
+ hr->suspended = GNUNET_NO;
+ MHD_resume_connection (hr->con);
+ }
+ }
+ if (NULL != curl_multi)
+ {
+ curl_multi_cleanup (curl_multi);
+ curl_multi = NULL;
+ }
+ if (NULL != curl_download_task)
+ {
+ GNUNET_SCHEDULER_cancel (curl_download_task);
+ curl_download_task = NULL;
+ }
+}
+
+
+/* *************** HTTP handling with cURL ***************** */
+
+
+/**
+ * Transform _one_ CURL header (gotten from the request) into
+ * MHD format and put it into the response headers list; mostly
+ * copies the headers, but makes special adjustments based on
+ * control requests.
+ *
+ * @param buffer curl buffer with a single
+ * line of header data; not 0-terminated!
+ * @param size curl blocksize
+ * @param nmemb curl blocknumber
+ * @param cls our `struct HttpRequest *`
+ * @return size of processed bytes
+ */
+static size_t
+curl_check_hdr (void *buffer,
+ size_t size,
+ size_t nmemb,
+ void *cls)
+{
+ struct HttpRequest *hr = cls;
+ struct HttpResponseHeader *header;
+ size_t bytes = size * nmemb;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *tok;
+
+ /* Raw line is not guaranteed to be null-terminated. */
+ ndup = GNUNET_malloc (bytes + 1);
+ memcpy (ndup,
+ buffer,
+ bytes);
+ ndup[bytes] = '\0';
+ hdr_type = strtok (ndup, ":");
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return bytes;
+ }
+ hdr_val = strtok (NULL, "");
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return bytes;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+
+ /* MHD does not allow certain characters in values,
+ * remove those, plus those could alter strings matching. */
+ if (NULL != (tok = strchr (hdr_val, '\n')))
+ *tok = '\0';
+ if (NULL != (tok = strchr (hdr_val, '\r')))
+ *tok = '\0';
+ if (NULL != (tok = strchr (hdr_val, '\t')))
+ *tok = '\0';
+ PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n",
+ hdr_type,
+ hdr_val);
+ /* Skip "Content-length:" header as it will be wrong, given
+ that we are man-in-the-middling the connection */
+ if (0 == strcasecmp (hdr_type,
+ MHD_HTTP_HEADER_CONTENT_LENGTH))
+ {
+ GNUNET_free (ndup);
+ return bytes;
+ }
+ /* Skip "Connection: Keep-Alive" header, it will be
+ done by MHD if possible */
+ if ( (0 == strcasecmp (hdr_type,
+ MHD_HTTP_HEADER_CONNECTION)) &&
+ (0 == strcasecmp (hdr_val,
+ "Keep-Alive")) )
+ {
+ GNUNET_free (ndup);
+ return bytes;
+ }
+ if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
+ {
+ header = GNUNET_new (struct HttpResponseHeader);
+ header->type = GNUNET_strdup (hdr_type);
+ header->value = GNUNET_strdup (hdr_val);
+ GNUNET_CONTAINER_DLL_insert (hr->header_head,
+ hr->header_tail,
+ header);
+ }
+ GNUNET_free (ndup);
+ return bytes;
+}
+
+
+/**
+ * Create the MHD response with CURL's as starting base;
+ * mainly set the response code and parses the response into
+ * JSON, if it is such.
+ *
+ * @param hr pointer to where to store the new data. Despite
+ * its name, the struct contains response data as well.
+ * @return #GNUNET_OK if it succeeds.
+ */
+static enum GNUNET_GenericReturnValue
+create_mhd_response_from_hr (struct HttpRequest *hr)
+{
+ long resp_code;
+
+ if (NULL != hr->response)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Response already set!\n");
+ return GNUNET_SYSERR;
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_getinfo (hr->curl,
+ CURLINFO_RESPONSE_CODE,
+ &resp_code));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Creating MHD response with code %u\n",
+ (unsigned int) resp_code);
+ hr->response_code = resp_code;
+ if (GNUNET_YES == hr->suspended)
+ {
+ MHD_resume_connection (hr->con);
+ hr->suspended = GNUNET_NO;
+ }
+ TALER_MHD_daemon_trigger ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle response payload data from cURL.
+ * Copies it into our `io_buf` to make it available to MHD.
+ *
+ * @param ptr pointer to the data
+ * @param size number of blocks of data
+ * @param nmemb blocksize
+ * @param ctx our `struct HttpRequest *`
+ * @return number of bytes handled
+ */
+static size_t
+curl_download_cb (void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *ctx)
+{
+ struct HttpRequest *hr = ctx;
+ size_t total = size * nmemb;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Curl download proceeding\n");
+
+ if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state)
+ {
+ /* Web server started with response before we finished
+ the upload. In this case, current libcurl decides
+ to NOT complete the upload, so we should jump in the
+ state machine to process the download, dropping the
+ rest of the upload. This should only really happen
+ with uploads without "Expect: 100 Continue" and
+ Web servers responding with an error (i.e. upload
+ not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
+ GNUNET_log
+ (GNUNET_ERROR_TYPE_INFO,
+ "Stopping %u byte upload: we are already downloading...\n",
+ (unsigned int) hr->io_len);
+ hr->io_len = 0;
+ }
+
+ if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Download callback goes to sleep\n");
+ hr->curl_paused = GNUNET_YES;
+ return CURL_WRITEFUNC_PAUSE;
+ }
+ GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED ==
+ hr->state);
+ if (hr->io_size - hr->io_len < total)
+ {
+ GNUNET_assert (total + hr->io_size >= total);
+ GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
+ GNUNET_array_grow (hr->io_buf,
+ hr->io_size,
+ GNUNET_MAX (total + hr->io_len,
+ hr->io_size * 2 + 1024));
+ }
+ GNUNET_memcpy (&hr->io_buf[hr->io_len],
+ ptr,
+ total);
+ hr->io_len += total;
+ return total;
+}
+
+
+/**
+ * Ask cURL for the select() sets and schedule cURL operations.
+ */
+static void
+curl_download_prepare (void);
+
+
+/**
+ * cURL callback for uploaded (PUT/POST) data.
+ * Copies from our `io_buf` to make it available to cURL.
+ *
+ * @param buf where to write the data
+ * @param size number of bytes per member
+ * @param nmemb number of members available in @a buf
+ * @param cls our `struct HttpRequest` that generated the data
+ * @return number of bytes copied to @a buf
+ */
+static size_t
+curl_upload_cb (void *buf,
+ size_t size,
+ size_t nmemb,
+ void *cls)
+{
+ struct HttpRequest *hr = cls;
+ size_t len = size * nmemb;
+ size_t to_copy;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Upload cb is working...\n");
+
+ if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
+ (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) )
+ {
+ GNUNET_log
+ (GNUNET_ERROR_TYPE_INFO,
+ "Upload cb aborts: we are already downloading...\n");
+ return CURL_READFUNC_ABORT;
+ }
+
+ if ( (0 == hr->io_len) &&
+ (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Pausing CURL UPLOAD, need more data\n");
+ return CURL_READFUNC_PAUSE;
+ }
+
+ /**
+ * We got rescheduled because the download callback was asleep.
+ * FIXME: can this block be eliminated and the unpausing being
+ * moved in the last block where we return zero as well?
+ */
+ if ( (0 == hr->io_len) &&
+ (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) )
+ {
+ if (GNUNET_YES == hr->curl_paused)
+ {
+ hr->curl_paused = GNUNET_NO;
+ curl_easy_pause (hr->curl,
+ CURLPAUSE_CONT);
+ }
+ curl_download_prepare ();
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Completed CURL UPLOAD\n");
+ return 0; /* upload finished, can now download */
+ }
+ to_copy = GNUNET_MIN (hr->io_len,
+ len);
+ GNUNET_memcpy (buf,
+ hr->io_buf,
+ to_copy);
+ /* shift remaining data back to the beginning of the buffer. */
+ memmove (hr->io_buf,
+ &hr->io_buf[to_copy],
+ hr->io_len - to_copy);
+ hr->io_len -= to_copy;
+ if (0 == hr->io_len)
+ {
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Completed CURL UPLOAD\n");
+ }
+ return to_copy;
+}
+
+
+/* ************** helper functions ************* */
+
+/**
+ * Extract the hostname from a complete URL.
+ *
+ * @param url full fledged URL
+ * @return pointer to the 0-terminated hostname, to be freed
+ * by the caller.
+ */
+static char *
+build_host_header (const char *url)
+{
+ #define MARKER "://"
+
+ char *header;
+ char *end;
+ char *hostname;
+ char *dup = GNUNET_strdup (url);
+
+ hostname = strstr (dup,
+ MARKER);
+ hostname += 3;
+ end = strchrnul (hostname, '/');
+ *end = '\0';
+ GNUNET_asprintf (&header,
+ "Host: %s",
+ hostname);
+ GNUNET_free (dup);
+ return header;
+}
+
+
+/* ************** main loop of cURL interaction ************* */
+
+
+/**
+ * Task that is run when we are ready to receive more data
+ * from curl
+ *
+ * @param cls closure
+ */
+static void
+curl_task_download (void *cls);
+
+
+/**
+ * Ask cURL for the select() sets and schedule cURL operations.
+ */
+static void
+curl_download_prepare ()
+{
+ CURLMcode mret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max;
+ struct GNUNET_NETWORK_FDSet *grs;
+ struct GNUNET_NETWORK_FDSet *gws;
+ long to;
+ struct GNUNET_TIME_Relative rtime;
+
+ if (NULL != curl_download_task)
+ {
+ GNUNET_SCHEDULER_cancel (curl_download_task);
+ curl_download_task = NULL;
+ }
+ max = -1;
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
+ &rs,
+ &ws,
+ &es,
+ &max)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s failed at %s:%d: `%s'\n",
+ "curl_multi_fdset",
+ __FILE__,
+ __LINE__,
+ curl_multi_strerror (mret));
+ return;
+ }
+ to = -1;
+ GNUNET_break (CURLM_OK ==
+ curl_multi_timeout (curl_multi,
+ &to));
+ if (-1 == to)
+ rtime = GNUNET_TIME_UNIT_FOREVER_REL;
+ else
+ rtime = GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_MILLISECONDS, to);
+ if (-1 != max)
+ {
+ grs = GNUNET_NETWORK_fdset_create ();
+ gws = GNUNET_NETWORK_fdset_create ();
+ GNUNET_NETWORK_fdset_copy_native (grs,
+ &rs,
+ max + 1);
+ GNUNET_NETWORK_fdset_copy_native (gws,
+ &ws,
+ max + 1);
+ curl_download_task
+ = GNUNET_SCHEDULER_add_select (
+ GNUNET_SCHEDULER_PRIORITY_DEFAULT,
+ rtime,
+ grs, gws,
+ &curl_task_download,
+ curl_multi);
+ GNUNET_NETWORK_fdset_destroy (gws);
+ GNUNET_NETWORK_fdset_destroy (grs);
+ }
+ else
+ {
+ curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime,
+ &curl_task_download,
+ curl_multi);
+ }
+}
+
+
+/**
+ * "Filter" function that translates MHD request headers to
+ * cURL's.
+ *
+ * @param cls our `struct HttpRequest`
+ * @param kind value kind
+ * @param key field key
+ * @param value field value
+ * @return #MHD_YES to continue to iterate
+ */
+static enum MHD_Result
+con_val_iter (void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *value)
+{
+ struct HttpRequest *hr = cls;
+ char *hdr;
+ char *new_value = NULL;
+
+ (void) kind;
+ if (0 == strcmp (MHD_HTTP_HEADER_HOST,
+ key))
+ {
+ /* We don't take the host header as given in the request.
+ * We'll instead put the proxied service's hostname in it*/
+ return MHD_YES;
+ }
+ if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
+ key)))
+ {
+ PAIVANA_LOG_INFO (
+ "Do not set Content-Length for request\n");
+ return MHD_YES;
+ }
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ key,
+ value);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding header `%s' to HTTP request\n",
+ hdr);
+ hr->headers = curl_slist_append (hr->headers,
+ hdr);
+ GNUNET_free (hdr);
+ GNUNET_free (new_value);
+ return MHD_YES;
+}
+
+
+/**
+ * Task that is run when we are ready to receive
+ * more data from curl.
+ *
+ * @param cls closure, usually NULL.
+ */
+static void
+curl_task_download (void *cls)
+{
+ int running;
+ int msgnum;
+ struct CURLMsg *msg;
+ CURLMcode mret;
+ struct HttpRequest *hr;
+
+ (void) cls;
+ curl_download_task = NULL;
+ do
+ {
+ running = 0;
+ mret = curl_multi_perform (curl_multi,
+ &running);
+ while (NULL != (msg = curl_multi_info_read (curl_multi,
+ &msgnum)))
+ {
+ GNUNET_break
+ (CURLE_OK == curl_easy_getinfo
+ (msg->easy_handle,
+ CURLINFO_PRIVATE,
+ (char **) &hr));
+
+ if (NULL == hr)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ switch (msg->msg)
+ {
+ case CURLMSG_NONE:
+ /* documentation says this is not used */
+ GNUNET_break (0);
+ break;
+ case CURLMSG_DONE:
+ switch (msg->data.result)
+ {
+ case CURLE_OK:
+ case CURLE_GOT_NOTHING:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "CURL download completed.\n");
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
+ if (NULL == hr->response)
+ GNUNET_assert (GNUNET_OK ==
+ create_mhd_response_from_hr (hr));
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Download curl failed: %s\n",
+ curl_easy_strerror (msg->data.result));
+ /* FIXME: indicate error somehow?
+ * close MHD connection badly as well? */
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
+ if (GNUNET_YES == hr->suspended)
+ {
+ MHD_resume_connection (hr->con);
+ hr->suspended = GNUNET_NO;
+ }
+ TALER_MHD_daemon_trigger ();
+ break;
+ }
+ if (NULL == hr->response)
+ hr->response = curl_failure_response;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Curl request for `%s' finished (got the response)\n",
+ hr->url);
+ TALER_MHD_daemon_trigger ();
+ break;
+ case CURLMSG_LAST:
+ /* documentation says this is not used */
+ GNUNET_break (0);
+ break;
+ default:
+ /* unexpected status code */
+ GNUNET_break (0);
+ break;
+ }
+ }
+ } while (mret == CURLM_CALL_MULTI_PERFORM);
+ if (CURLM_OK != mret)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s failed at %s:%d: `%s'\n",
+ "curl_multi_perform",
+ __FILE__,
+ __LINE__,
+ curl_multi_strerror (mret));
+ if (0 == running)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Suspending cURL multi loop,"
+ " no more events pending\n");
+ return; /* nothing more in progress */
+ }
+ curl_download_prepare ();
+}
+
+
+struct HttpRequest *
+PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection,
+ const char *url)
+{
+ struct HttpRequest *hr;
+
+ hr = GNUNET_new (struct HttpRequest);
+ hr->con = connection;
+ hr->url = GNUNET_strdup (url);
+ GNUNET_CONTAINER_DLL_insert (hr_head,
+ hr_tail,
+ hr);
+ return hr;
+}
+
+
+void
+PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr)
+{
+ struct HttpResponseHeader *header;
+
+ if (NULL != hr->curl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Resetting cURL handle\n");
+ curl_multi_remove_handle (curl_multi,
+ hr->curl);
+ curl_easy_cleanup (hr->curl);
+ hr->curl = NULL;
+ hr->io_len = 0;
+ }
+ if (NULL != hr->headers)
+ {
+ curl_slist_free_all (hr->headers);
+ hr->headers = NULL;
+ }
+ if ( (NULL != hr->response) &&
+ (curl_failure_response != hr->response) )
+ /* Destroy non-error responses... (?) */
+ MHD_destroy_response (hr->response);
+
+ for (header = hr->header_head;
+ header != NULL;
+ header = hr->header_head)
+ {
+ GNUNET_CONTAINER_DLL_remove (hr->header_head,
+ hr->header_tail,
+ header);
+ GNUNET_free (header->type);
+ GNUNET_free (header->value);
+ GNUNET_free (header);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Proxying of '%s' completely done\n",
+ hr->url);
+
+ GNUNET_free (hr->url);
+ GNUNET_free (hr->io_buf);
+ GNUNET_CONTAINER_DLL_remove (hr_head,
+ hr_tail,
+ hr);
+ GNUNET_free (hr);
+}
+
+
+/**
+ * Main MHD callback for reverse proxy.
+ *
+ * @param hr the HTTP request context
+ * @param con MHD connection handle
+ * @param url the url in the request
+ * @param meth the HTTP method used ("GET", "PUT", etc.)
+ * @param ver the HTTP version string (i.e. "HTTP/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
+ * upload_data)
+ * @param 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;
+ * @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
+ */
+enum MHD_Result
+PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
+ struct MHD_Connection *con,
+ const char *url,
+ const char *meth,
+ const char *ver,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ /* FIXME: make state machine more explicit by
+ switching on hr->state here! */
+ if (0 != *upload_data_size)
+ {
+ GNUNET_assert
+ (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Processing %u bytes UPLOAD\n",
+ (unsigned int) *upload_data_size);
+
+ /* Grow the buffer if remaining space isn't enough. */
+ if (hr->io_size - hr->io_len < *upload_data_size)
+ {
+ /* How can this assertion be false? */
+ GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
+ /* This asserts that upload_data_size > 0, ? */
+ GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len);
+
+ GNUNET_array_grow (hr->io_buf,
+ hr->io_size,
+ GNUNET_MAX
+ (hr->io_size * 2 + 1024,
+ *upload_data_size + hr->io_len));
+ }
+
+ /* Finally copy upload data. */
+ GNUNET_memcpy (&hr->io_buf[hr->io_len],
+ upload_data,
+ *upload_data_size);
+
+ hr->io_len += *upload_data_size;
+ *upload_data_size = 0;
+
+ return MHD_YES;
+ }
+
+ /* Upload (*from the client*) finished or just a without-body
+ * request. */
+ if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state)
+ {
+ hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Finished processing UPLOAD\n");
+ }
+
+ /* generate curl request to the proxied service. */
+ if (NULL == hr->curl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Generating curl request\n");
+ hr->curl = curl_easy_init ();
+ if (NULL == hr->curl)
+ {
+ PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
+ return MHD_queue_response (con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ curl_failure_response);
+ }
+
+ /* No need to check whether we're POSTing or PUTting.
+ * If not needed, one of the following values will be
+ * ignored.*/
+ curl_easy_setopt (hr->curl,
+ CURLOPT_POSTFIELDSIZE,
+ hr->io_len);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_INFILESIZE,
+ hr->io_len);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_HEADERFUNCTION,
+ &curl_check_hdr);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_HEADERDATA,
+ hr);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_FOLLOWLOCATION,
+ 0);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_CONNECTTIMEOUT,
+ 60L);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_TIMEOUT,
+ 60L);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_NOSIGNAL,
+ 1L);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_PRIVATE,
+ hr);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_VERBOSE,
+ 0);
+
+ curl_easy_setopt (hr->curl,
+ CURLOPT_READFUNCTION,
+ &curl_upload_cb);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_READDATA,
+ hr);
+
+ curl_easy_setopt (hr->curl,
+ CURLOPT_WRITEFUNCTION,
+ &curl_download_cb);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_WRITEDATA,
+ hr);
+ {
+ char *curlurl;
+ char *host_hdr;
+
+ GNUNET_asprintf (&curlurl,
+ "%s%s",
+ target_server_base_url,
+ hr->url);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_URL,
+ curlurl);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Forwarding request to: %s\n",
+ curlurl);
+ GNUNET_free (curlurl);
+
+ host_hdr = build_host_header (target_server_base_url);
+ PAIVANA_LOG_DEBUG ("Faking the host header, %s\n",
+ host_hdr);
+ hr->headers = curl_slist_append (hr->headers,
+ host_hdr);
+ GNUNET_free (host_hdr);
+ }
+
+ // FIXME: support PATCH, etc.
+ if (0 == strcasecmp (meth,
+ MHD_HTTP_METHOD_PUT))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Crafting a CURL PUT request\n");
+
+ curl_easy_setopt (hr->curl,
+ CURLOPT_UPLOAD,
+ 1L);
+ hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
+ }
+ else if (0 == strcasecmp (meth,
+ MHD_HTTP_METHOD_POST))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Crafting a CURL POST request\n");
+ curl_easy_setopt (hr->curl,
+ CURLOPT_POST,
+ 1L);
+ curl_easy_setopt (hr->curl,
+ CURLOPT_VERBOSE,
+ 1L);
+ hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
+ }
+ else if (0 == strcasecmp (meth,
+ MHD_HTTP_METHOD_HEAD))
+ {
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
+ curl_easy_setopt (hr->curl,
+ CURLOPT_NOBODY,
+ 1L);
+ }
+ else if (0 == strcasecmp (meth,
+ MHD_HTTP_METHOD_OPTIONS))
+ {
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
+ curl_easy_setopt (hr->curl,
+ CURLOPT_CUSTOMREQUEST,
+ "OPTIONS");
+ }
+ else if (0 == strcasecmp (meth,
+ MHD_HTTP_METHOD_GET))
+ {
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
+ curl_easy_setopt (hr->curl,
+ CURLOPT_HTTPGET,
+ 1L);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unsupported HTTP method `%s'\n",
+ meth);
+ curl_easy_cleanup (hr->curl);
+ hr->curl = NULL;
+ return MHD_NO;
+ }
+
+ if (CURLM_OK !=
+ curl_multi_add_handle (curl_multi,
+ hr->curl))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (hr->curl);
+ hr->curl = NULL;
+ return MHD_NO;
+ }
+
+ MHD_get_connection_values (con,
+ MHD_HEADER_KIND,
+ &con_val_iter,
+ hr);
+
+ curl_easy_setopt (hr->curl,
+ CURLOPT_HTTPHEADER,
+ hr->headers);
+ curl_download_prepare ();
+
+ return MHD_YES;
+ }
+
+ if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state)
+ {
+ GNUNET_assert (GNUNET_NO == hr->suspended);
+ MHD_suspend_connection (con);
+ hr->suspended = GNUNET_YES;
+ return MHD_YES; /* wait for curl */
+ }
+
+ GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state);
+
+ hr->response
+ = MHD_create_response_from_buffer_copy (hr->io_len,
+ hr->io_buf);
+ for (struct HttpResponseHeader *header = hr->header_head;
+ NULL != header;
+ header = header->next)
+ {
+ const char *value = header->value;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding MHD response header %s->%s\n",
+ header->type,
+ value);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (hr->response,
+ header->type,
+ value));
+ }
+ TALER_MHD_daemon_trigger ();
+
+ return MHD_queue_response (con,
+ hr->response_code,
+ hr->response);
+}
diff --git a/src/backend/paivana-httpd_reverse.h b/src/backend/paivana-httpd_reverse.h
@@ -0,0 +1,81 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2026 Taler Systems SA
+
+ Paivana 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.
+
+ Paivana 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 Paivana; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 51 Franklin
+ Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+/**
+ * @author Christian Grothoff
+ * @file paivana-httpd_reverse.h
+ *
+ * @brief Project data definition
+ */
+#ifndef PAIVANA_HTTPD_REVERSE_H
+#define PAIVANA_HTTPD_REVERSE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "paivana-httpd.h"
+
+bool
+PAIVANA_HTTPD_reverse_init (void);
+
+
+void
+PAIVANA_HTTPD_reverse_shutdown (void);
+
+
+struct HttpRequest *
+PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection,
+ const char *url);
+
+/**
+ * Main MHD callback for reverse proxy.
+ *
+ * @param hr the HTTP request context
+ * @param con MHD connection handle
+ * @param url the url in the request
+ * @param meth the HTTP method used ("GET", "PUT", etc.)
+ * @param ver the HTTP version string (i.e. "HTTP/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
+ * upload_data)
+ * @param 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;
+ * @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
+ */
+enum MHD_Result
+PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
+ struct MHD_Connection *con,
+ const char *url,
+ const char *meth,
+ const char *ver,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+void
+PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr);
+
+#endif