paivana

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

commit f27f2fd061cb3b86cb48c972a217d06ee72ccd3e
parent 1abd14d4b9c9643ef9b14423e8cb633fc9d07c19
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon,  3 Aug 2026 21:11:13 +0200

bound number of templates we cache

Diffstat:
Msrc/backend/paivana-httpd_templates.c | 46++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+), 0 deletions(-)

diff --git a/src/backend/paivana-httpd_templates.c b/src/backend/paivana-httpd_templates.c @@ -45,6 +45,17 @@ struct Template; /** + * Maximum number of rendered paywall responses we cache per template. + * The cache is keyed on the client-supplied Accept-Language / + * Accept-Encoding headers; without a bound an attacker could send + * unlimited distinct header values and grow the cache without limit + * (memory-exhaustion DoS on the cheap pre-payment path). When the + * cap is reached we evict the oldest entry (FIFO). + */ +#define MAX_RESPONSE_CACHE_ENTRIES 128 + + +/** * Entry in the cache of responses for a given template. */ struct ResponseCacheEntry @@ -135,6 +146,11 @@ struct Template struct TALER_MERCHANT_GetPrivateTemplateHandle *gt; /** + * Number of entries in the template cache starting at @e rce_head. + */ + unsigned int rce_length; + + /** * Kept in a DLL. */ struct ResponseCacheEntry *rce_head; @@ -267,9 +283,21 @@ load_paywall (struct MHD_Connection *conn, pos->lang)) && (eq (ae, pos->ae) ) ) + { + if (t->rce_head != pos) + { + /* Hit, move pos to head of DLL for proper LRU eviction */ + GNUNET_CONTAINER_DLL_remove (t->rce_head, + t->rce_tail, + pos); + GNUNET_CONTAINER_DLL_insert (t->rce_head, + t->rce_tail, + pos); + } return MHD_queue_response (conn, pos->http_status, pos->paywall); + } } { @@ -354,6 +382,21 @@ load_paywall (struct MHD_Connection *conn, { struct ResponseCacheEntry *rce; + while (t->rce_length > MAX_RESPONSE_CACHE_ENTRIES) + { + /* Evict the oldest entry (inserted at head, so tail is oldest). */ + struct ResponseCacheEntry *old = t->rce_tail; + + GNUNET_CONTAINER_DLL_remove (t->rce_head, + t->rce_tail, + old); + GNUNET_assert (t->rce_length > 0); + t->rce_length--; + MHD_destroy_response (old->paywall); + GNUNET_free (old->ae); + GNUNET_free (old->lang); + GNUNET_free (old); + } rce = GNUNET_new (struct ResponseCacheEntry); if (NULL != lang) rce->lang = GNUNET_strdup (lang); @@ -361,6 +404,7 @@ load_paywall (struct MHD_Connection *conn, rce->ae = GNUNET_strdup (ae); rce->paywall = reply; rce->http_status = http_status; + t->rce_length++; GNUNET_CONTAINER_DLL_insert (t->rce_head, t->rce_tail, rce); @@ -693,6 +737,8 @@ PAIVANA_HTTPD_unload_templates () { struct ResponseCacheEntry *rce = t->rce_head; + GNUNET_assert (t->rce_length > 0); + t->rce_length--; GNUNET_CONTAINER_DLL_remove (t->rce_head, t->rce_tail, rce);