paivana

HTTP paywall reverse proxy
Log | Files | Refs | README | LICENSE

commit 241a5e964502180add6c2e5bbb18eb4e61bb4238
parent 6c993afa87f882acf31de13af4f551b5f8171f0e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 20 Apr 2026 18:50:29 +0200

start to implement template logic

Diffstat:
Msrc/backend/paivana-httpd.c | 16+++++++++-------
Msrc/backend/paivana-httpd.h | 6++++++
Msrc/backend/paivana-httpd_daemon.c | 5++---
Msrc/backend/paivana-httpd_daemon.h | 4+---
Msrc/backend/paivana-httpd_templates.c | 283++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/backend/paivana-httpd_templates.h | 12++++--------
6 files changed, 283 insertions(+), 43 deletions(-)

diff --git a/src/backend/paivana-httpd.c b/src/backend/paivana-httpd.c @@ -54,14 +54,15 @@ int PH_no_check; int PH_global_ret; /** - * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). + * Our configuration. */ -static struct GNUNET_CURL_RescheduleContext *ctx_rc; +const struct GNUNET_CONFIGURATION_Handle *PH_cfg; + /** - * Our configuration. + * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). */ -static const struct GNUNET_CONFIGURATION_Handle *cfg; +static struct GNUNET_CURL_RescheduleContext *ctx_rc; /* *************** General / main code *************** */ @@ -121,7 +122,7 @@ run (void *cls, (void) cls; (void) args; (void) cfgfile; - cfg = c; + PH_cfg = c; if (! PAIVANA_HTTPD_reverse_init ()) { @@ -254,8 +255,9 @@ run (void *cls, GNUNET_free (auth_header); } ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_ctx); - PAIVANA_HTTPD_load_templates (&PAIVANA_HTTPD_serve_requests, - (void *) c); + /* Once templates are done loading, this will + start the daemon as well */ + PAIVANA_HTTPD_load_templates (); } diff --git a/src/backend/paivana-httpd.h b/src/backend/paivana-httpd.h @@ -69,4 +69,10 @@ extern int PH_no_check; */ extern int PH_global_ret; +/** + * Our configuration. + */ +extern const struct GNUNET_CONFIGURATION_Handle *PH_cfg; + + #endif diff --git a/src/backend/paivana-httpd_daemon.c b/src/backend/paivana-httpd_daemon.c @@ -313,12 +313,11 @@ start_daemon (void *cls, void -PAIVANA_HTTPD_serve_requests (void *cls) +PAIVANA_HTTPD_serve_requests () { - const struct GNUNET_CONFIGURATION_Handle *cfg = cls; enum GNUNET_GenericReturnValue ret; - ret = TALER_MHD_listen_bind (cfg, + ret = TALER_MHD_listen_bind (PH_cfg, "paivana", &start_daemon, NULL); diff --git a/src/backend/paivana-httpd_daemon.h b/src/backend/paivana-httpd_daemon.h @@ -33,11 +33,9 @@ /** * Start the HTTP server and begin serving requests. - * - * @param cls pass the `const struct GNUNET_CONFIGURATION_Handle` */ void -PAIVANA_HTTPD_serve_requests (void *cls); +PAIVANA_HTTPD_serve_requests (void); #endif diff --git a/src/backend/paivana-httpd_templates.c b/src/backend/paivana-httpd_templates.c @@ -28,29 +28,97 @@ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include "paivana-httpd.h" +#include "paivana-httpd_daemon.h" #include "paivana-httpd_templates.h" #include <taler/taler_mhd_lib.h> #include "paivana_pd.h" +#include <regex.h> -// FIXME: make part of our template structure... +struct Template; +#define TALER_MERCHANT_GET_PRIVATE_TEMPLATE_RESULT_CLOSURE struct Template +#include <taler/merchant/get-private-templates-TEMPLATE_ID.h> +#include <taler/merchant/get-private-templates.h> + +/** + * Information about a template in the merchant backend. + */ +struct Template +{ + + /** + * Kept in a DLL. + */ + struct Template *next; + + /** + * Kept in a DLL. + */ + struct Template *prev; + + /** + * ID of the template. + */ + char *template_id; + + /** + * Regular expression of websites the template is for. + */ + char *regex; + + /** + * Pre-compiled regular expression @e regex. + */ + regex_t ex; + + /** + * Handle used to request more information about the template. + */ + struct TALER_MERCHANT_GetPrivateTemplateHandle *gt; + + /** + * Paywall response for this template. NULL for no paywall. + */ + struct MHD_Response *paywall; + +}; + + +/** + * Kept in a DLL. + */ +static struct Template *t_head; + /** - * Static paywall response. + * Kept in a DLL. */ -static struct MHD_Response *paywall; +static struct Template *t_tail; + + +/** + * Handle to get all the templates. + */ +static struct TALER_MERCHANT_GetPrivateTemplatesHandle *gpt; /** * Try to initialize the paywall response. + * + * @param template_id template to use for the response + * @return HTTP response to return for matching websites */ -static bool -load_paywall (void) +static struct MHD_Response * +load_paywall (const char *template_id) { + struct MHD_Response *paywall; char *tpath; char *fn; int fd; struct stat sb; + // FIXME: use templating logic to modify HTML + // based on template_id, backend base URL and + // possibly other values! tpath = GNUNET_OS_installation_get_path (PAIVANA_project_data (), GNUNET_OS_IPK_DATADIR); GNUNET_asprintf (&fn, @@ -76,43 +144,190 @@ load_paywall (void) fn); GNUNET_free (fn); GNUNET_break (0 == close (fd)); - return false; + return NULL; } GNUNET_free (fn); paywall = MHD_create_response_from_fd (sb.st_size, fd); if (NULL == paywall) - return false; + return NULL; GNUNET_break (MHD_YES == MHD_add_response_header (paywall, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html")); - return true; + return paywall; +} + + +/** + * Parse template contract to (mostly) determine the + * regex specifying which websites the template applies to. + * + * @param[in,out] t template to update + * @param contract contract to parse + */ +static void +parse_template (struct Template *t, + const json_t *contract) +{ + const char *regex = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("website_regex", + &regex), + NULL), + GNUNET_JSON_spec_end () + }; + const char *en; + + if (GNUNET_OK != + GNUNET_JSON_parse ((json_t *) contract, + spec, + &en, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid template %s at field %s\n", + t->template_id, + en); + return; + } + if (0 != regcomp (&t->ex, + regex, + REG_NOSUB | REG_EXTENDED)) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid regex in template %s: %s\n", + t->template_id, + regex); + return; + } + t->regex = GNUNET_strdup (regex); +} + + +/** + * Callback for a GET /private/templates/$TEMPLATE_ID request. + * + * @param cls closure + * @param tgr response details + */ +static void +setup_template ( + struct Template *t, + const struct TALER_MERCHANT_GetPrivateTemplateResponse *tgr) +{ + t->gt = NULL; + switch (tgr->hr.http_status) + { + case MHD_HTTP_OK: + parse_template (t, + tgr->details.ok.template_contract); + break; + default: + GNUNET_break (0); + break; + } + if (t->regex) + { + t->paywall = load_paywall (t->template_id); + } + for (struct Template *p = t_head; NULL != p; p = p->next) + if (NULL != p->gt) + return; + /* all templates done, continue with main logic */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Templates loaded, starting to serve requests\n"); + PAIVANA_HTTPD_serve_requests (); +} + + +/** + * Callback for a GET /private/templates request. + * + * @param cls closure + * @param tgr response details + */ +static void +check_templates ( + void *cls, + const struct TALER_MERCHANT_GetPrivateTemplatesResponse *tgr) +{ + gpt = NULL; + switch (tgr->hr.http_status) + { + case MHD_HTTP_OK: + break; + case MHD_HTTP_NO_CONTENT: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No templates found, starting to serve requests\n"); + PAIVANA_HTTPD_serve_requests (); + return; + default: + GNUNET_break (0); + PH_global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (0 == tgr->details.ok.templates_length) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No templates found, starting to serve requests\n"); + PAIVANA_HTTPD_serve_requests (); + return; + } + + for (unsigned int i = 0; i<tgr->details.ok.templates_length; i++) + { + const struct TALER_MERCHANT_GetPrivateTemplatesTemplateEntry *te + = &tgr->details.ok.templates[i]; + struct Template *t; + + t = GNUNET_new (struct Template); + t->template_id = GNUNET_strdup (te->template_id); + t->gt = TALER_MERCHANT_get_private_template_create (PH_ctx, + PH_merchant_base_url, + t->template_id); + GNUNET_CONTAINER_DLL_insert (t_head, + t_tail, + t); + GNUNET_assert ( + TALER_EC_NONE == + TALER_MERCHANT_get_private_template_start (t->gt, + &setup_template, + t)); + } } void -PAIVANA_HTTPD_load_templates (GNUNET_SCHEDULER_TaskCallback cb, - void *cb_cls) +PAIVANA_HTTPD_load_templates () { - // FIXME: not implemented! - GNUNET_SCHEDULER_add_now (cb, - cb_cls); - // -> Note: needs *restart* on template changes! - // -> in future: long-poll on GET /private/templates? + gpt = TALER_MERCHANT_get_private_templates_create (PH_ctx, + PH_merchant_base_url); + GNUNET_assert (NULL != gpt); + GNUNET_assert ( + TALER_EC_NONE == + TALER_MERCHANT_get_private_templates_start (gpt, + &check_templates, + NULL)); } struct MHD_Response * PAIVANA_HTTPD_search_templates (const char *website) { - // FIXME: check if url is one that we require payment for, - // if not return NULL - // (also should determine WHICH payment template - // we use => load *all* templates of the instance, - // pre-compile *all* regexes and then match!) - - // FIXME: not implemented + for (struct Template *t = t_head; NULL != t; t = t->next) + { + if (NULL == t->regex) + continue; + if (0 == regexec (&t->ex, + website, + 0, NULL, + 0)) + return t->paywall; + } return NULL; } @@ -123,4 +338,28 @@ PAIVANA_HTTPD_search_templates (const char *website) void PAIVANA_HTTPD_unload_templates () { + while (NULL != t_head) + { + struct Template *t = t_head; + + GNUNET_CONTAINER_DLL_remove (t_head, + t_tail, + t); + if (NULL != t->gt) + TALER_MERCHANT_get_private_template_cancel (t->gt); + if (NULL != t->paywall) + MHD_destroy_response (t->paywall); + if (NULL != t->regex) + { + regfree (&t->ex); + GNUNET_free (t->regex); + } + GNUNET_free (t->template_id); + GNUNET_free (t); + } + if (NULL != gpt) + { + TALER_MERCHANT_get_private_templates_cancel (gpt); + gpt = NULL; + } } diff --git a/src/backend/paivana-httpd_templates.h b/src/backend/paivana-httpd_templates.h @@ -31,16 +31,12 @@ /** - * Load the templates from the merchant backend. - * Calls @a cb upon completion if successful, otherwise - * may initiate shutdown. - * - * @param cb continuation to call when done - * @param cb_cls closure for @a cb + * Load the templates from the merchant backend. Calls + * PAIVANA_HTTPD_serve_requests() upon completion if successful, + * otherwise may initiate shutdown. */ void -PAIVANA_HTTPD_load_templates (GNUNET_SCHEDULER_TaskCallback cb, - void *cb_cls); +PAIVANA_HTTPD_load_templates (void); /**