paivana

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

commit c71b1c811a2593c2b84b1f74714ddef283a07630
parent ef78343baca4dddb99139d42e7e2bbfc061da77c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 26 Apr 2026 22:48:40 +0200

do not 502 on early response

Diffstat:
Msrc/backend/paivana-httpd_reverse.c | 100+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 52 insertions(+), 48 deletions(-)

diff --git a/src/backend/paivana-httpd_reverse.c b/src/backend/paivana-httpd_reverse.c @@ -742,27 +742,28 @@ curl_download_cb (void *ptr, 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 = true; - return CURL_WRITEFUNC_PAUSE; + if (0 == hr->io_len) + { + /* Either the request body was empty (CURLOPT_POSTFIELDSIZE = 0, + so libcurl never called our read callback) or the upload + already drained but we have not yet entered upload_cb to + flip the state — either way, the upload is complete and we + can move straight on to consuming the response. */ + hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; + } + else + { + /* HTTP/1.1 lets the upstream begin responding before we have + finished sending the request body. Pause the download + callback (libcurl will buffer the response bytes for us) + and let curl_upload_cb finish draining io_buf; it will + resume us via curl_easy_pause() once io_len hits zero. */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing download cb, %u upload bytes still to send\n", + (unsigned int) hr->io_len); + hr->curl_paused = true; + return CURL_WRITEFUNC_PAUSE; + } } GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state); @@ -813,42 +814,35 @@ curl_upload_cb (void *buf, 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) ) + if (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) { - GNUNET_log - (GNUNET_ERROR_TYPE_INFO, - "Upload cb aborts: we are already downloading...\n"); + /* Should not happen: the curl task only declares the transfer + done after consuming POSTFIELDSIZE bytes from us. */ + GNUNET_break (0); return CURL_READFUNC_ABORT; } - if ( (0 == hr->io_len) && - (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) ) + if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Pausing CURL UPLOAD, need more data\n"); - return CURL_READFUNC_PAUSE; + /* We have already drained io_buf and flipped the state in a + previous call. With CURLOPT_POSTFIELDSIZE set, libcurl + should not ask for more bytes — but if it does, signal a + clean end-of-upload rather than aborting the transfer. */ + GNUNET_assert (0 == hr->io_len); + return 0; } - /** - * 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) ) + GNUNET_assert (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state); + if (0 == hr->io_len) { - if (hr->curl_paused) - { - hr->curl_paused = false; - 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 */ + /* Should not happen: start_curl_request runs only after the + client body is fully buffered, so io_len begins > 0 (or 0 + for an empty body, which CURLOPT_POSTFIELDSIZE = 0 keeps + libcurl from asking about). */ + GNUNET_break (0); + return CURL_READFUNC_PAUSE; } + to_copy = GNUNET_MIN (hr->io_len, len); GNUNET_memcpy (buf, @@ -864,6 +858,16 @@ curl_upload_cb (void *buf, hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Completed CURL UPLOAD\n"); + if (hr->curl_paused) + { + /* curl_download_cb pauses itself when an early upstream + response arrives mid-upload; resume it now that the + request body has been fully delivered. */ + hr->curl_paused = false; + curl_easy_pause (hr->curl, + CURLPAUSE_CONT); + curl_download_prepare (); + } } return to_copy; }