/*
     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 Affero 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 Affero General Public License for more details.

     You should have received a copy of the GNU Affero 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 globals for the HTTPD reverse proxy
 */
#ifndef PAIVANA_HTTPD_H
#define PAIVANA_HTTPD_H

#include <regex.h>
#include <stdbool.h>

/**
 * The de-facto forwarding headers.  libmicrohttpd only defines the
 * standardized #MHD_HTTP_HEADER_FORWARDED (RFC 7239), but these are
 * what nginx, Apache and everything else in front of us actually
 * emit.  They are consumed on the way in (client address, base URL)
 * and produced on the way out (proxying), in different files, so
 * they are spelled out once here.
 */
#define PH_HEADER_X_FORWARDED_FOR "X-Forwarded-For"
#define PH_HEADER_X_FORWARDED_PROTO "X-Forwarded-Proto"
#define PH_HEADER_X_FORWARDED_HOST "X-Forwarded-Host"
#define PH_HEADER_X_FORWARDED_PORT "X-Forwarded-Port"

/**
 * Longest URL we are willing to run a regular expression over.
 *
 * Both the WHITELIST expression and the ones from the merchant's
 * templates are matched against client-controlled URLs on the
 * pre-payment path, and neither is validated beyond regcomp(3)
 * succeeding.  A careless expression can exhibit catastrophic
 * backtracking, whose cost then grows super-linearly in the length of
 * the subject; capping the subject length bounds that cost.  16 kb is
 * far above any legitimate URL and at (or above) the request line
 * limit of the usual front-end servers.
 */
#define PH_MAX_URL_LENGTH (16 * 1024)

#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 *PH_target_server_base_url;

/**
 * Replace the connection to target server.
 * File path to the unix socket
 */
extern char *PH_target_server_unixpath;

/**
 * Merchant backend base URL.
 */
extern char *PH_merchant_base_url;

/**
 * Base URL of this site as seen by the client. If not set,
 * we will try to determine it from "X-Forwarded-Host" and
 * "Host" and "X-Forwarded-Port" headers.
 */
extern char *PH_base_url;

/**
 * Curl context for talking to the merchant backend.  Carries the
 * `Authorization: Bearer $MERCHANT_ACCESS_TOKEN` header on every
 * request, so it must never be used for anything but the backend.
 * NULL in `-n` (no payment) mode, where there is no backend.
 */
extern struct GNUNET_CURL_Context *PH_merchant_ctx;

/**
 * Curl context for forwarding client requests to the origin server.
 * Deliberately separate from #PH_merchant_ctx: headers appended to a
 * context apply to every request made through it, and our credentials
 * for the merchant backend have no business being sent to the site we
 * proxy for.
 */
extern struct GNUNET_CURL_Context *PH_proxy_ctx;

/**
 * Pre-compiled regular expression for sites that are whitelisted
 * and never paywalled.
 */
extern regex_t PH_whitelist_ex;

/**
 * True if whitelist_ex was set.
 */
extern bool PH_have_whitelist_ex;

/**
 * Set to true if the cookie applies globally to all sites
 * and not per-page.
 */
extern int PH_global_cookie;

/**
 * Disable paywall check.
 */
extern int PH_no_check;

/**
 * If set, we are behind a reverse proxy: the socket peer is a proxy we
 * trust, and the forwarding headers ("Forwarded" and the "X-Forwarded-*"
 * family) are read to recover what the client actually did.  Unset, no
 * forwarding header is consulted at all and the peer *is* the client.
 * Only enable it when the server in front writes those headers itself,
 * either setting them or appending to them.  Appending is enough --
 * what it appends is the address it accepted the request from, so the
 * element we read is still one it vouches for.  What is not safe is a
 * front server that passes the client's own headers through unchanged,
 * which leaves the client writing the element we believe.
 *
 * This flag alone extends trust exactly one hop, to the peer, so with
 * no #PH_trusted_proxies4 / #PH_trusted_proxies6 the client is the
 * RIGHTMOST element of the chain -- the only one the peer vouches for.
 * Naming further networks there extends the walk leftwards, one hop per
 * trusted node; see PAIVANA_HTTPD_resolve_forwarding().
 *
 * It also decides whether `BASE_URL` is optional: the flag is the
 * assertion that the proxy enforced a correct "Host", which is what
 * makes reconstructing our own URL from the request safe.
 */
extern int PH_respect_forwarded_headers;

/**
 * Networks whose members are reverse proxies we trust to report the
 * client address truthfully, from the `TRUSTED_PROXIES` configuration
 * option.  NULL if unconfigured.  These are the hops BEYOND the socket
 * peer: #PH_respect_forwarded_headers already trusts the peer, and each
 * network named here lets the walk step one element further left, past
 * a node it matches.  The first node not covered is the client.
 */
extern struct GNUNET_STRINGS_IPv4NetworkPolicy *PH_trusted_proxies4;

/**
 * IPv6 counterpart of #PH_trusted_proxies4, from `TRUSTED_PROXIES6`.
 */
extern struct GNUNET_STRINGS_IPv6NetworkPolicy *PH_trusted_proxies6;

/**
 * True if either #PH_trusted_proxies4 or #PH_trusted_proxies6 was
 * configured.  Distinguishes "no hop beyond the peer is trusted" from
 * "these networks are, in addition to the peer".
 */
extern bool PH_have_trusted_proxies;

/**
 * Value to return from main()
 */
extern int PH_global_ret;

/**
 * Our configuration.
 */
extern const struct GNUNET_CONFIGURATION_Handle *PH_cfg;

/**
 * Total number of concurrent client connections we accept, from the
 * `CONNECTION_LIMIT` configuration option.  Divided evenly over the
 * listen sockets that come up, because MHD's own limit is per daemon
 * and TALER_MHD_listen_bind() starts one daemon per address.
 *
 * MHD's default (about 1018 per daemon) is wrong here in both
 * directions: it is not process-wide, and it does not know that we
 * spend file descriptors on outbound libcurl handles from the same
 * table -- exhausting them makes *paying* clients' requests fail.
 */
extern unsigned int PH_connection_limit;

/**
 * Number of concurrent connections we accept from any single client
 * address, from `PER_IP_CONNECTION_LIMIT`; 0 disables the check, which
 * is MHD's default.
 *
 * Must be 0 wherever the peer address is not the client's: under
 * `SERVE = unix` or `systemd` every client shares one peer, and behind
 * a reverse proxy or a NAT many clients do.  See the README.
 */
extern unsigned int PH_per_ip_connection_limit;

/**
 * How many bytes of a request body we hold in memory at once while
 * relaying it upstream, from `REQUEST_BUFFER_MAX` or the `-u` /
 * `--max-upload` command-line option; 256 KiB by default.
 *
 * A throughput knob, not a limit: the body is streamed, so this bounds
 * only how far the client may run ahead of the origin before we stop
 * reading from it.  What an upload is *allowed* to be is
 * #PH_max_request_size.  Larger means fewer suspend/resume round trips
 * on a fast link and more memory per request in flight; the worst case
 * is the product with #PH_connection_limit.
 *
 * Bounds the *proxied* path only.  Bodies sent to our own endpoints
 * never reach this buffer: `POST /.well-known/paivana` is read by
 * TALER_MHD_parse_post_json(), whose limit is
 * #TALER_MHD_REQUEST_BUFFER_MAX.
 */
extern unsigned long long PH_request_buffer_max;

/**
 * How many bytes of a response body we hold in memory at once while
 * relaying it to the client, from `RESPONSE_BUFFER_MAX`; 256 KiB by
 * default.  The mirror of #PH_request_buffer_max, and equally not a
 * limit — there is deliberately no ceiling on the size of a response.
 */
extern unsigned long long PH_response_buffer_max;

/**
 * Largest request body we accept, from `MAX_REQUEST_SIZE`; 1 MiB by
 * default.  Anything above it is answered 413, on the declared
 * `Content-Length` where there is one and otherwise once the body
 * actually exceeds it.
 *
 * Distinct from #PH_request_buffer_max, which used to do both jobs
 * because a body that could not be buffered could not be forwarded.
 * Streaming separates them: this one is policy, that one is memory.
 * For the sake of configurations written when they were one number,
 * an explicitly configured `REQUEST_BUFFER_MAX` with no
 * `MAX_REQUEST_SIZE` beside it still sets both.
 */
extern unsigned long long PH_max_request_size;

/**
 * How long the origin has to produce its response *headers* before we
 * give up on it and answer 504, from `UPSTREAM_TIMEOUT`; 60 s by
 * default.
 *
 * Deliberately not a bound on the whole request: a response is
 * relayed as it arrives, so a large one legitimately runs for as long
 * as it runs.  This clock is cancelled the moment the final header
 * section ends, and it is the only one of the three that can still
 * produce a status code — after that the status is already on the
 * wire.  A stalling origin is caught afterwards by
 * #PH_upstream_stall_timeout instead.
 */
extern struct GNUNET_TIME_Relative PH_upstream_timeout;

/**
 * How long the origin may make no progress at all — no byte moved in
 * either direction — before we give up on the request, from
 * `UPSTREAM_STALL_TIMEOUT`; 60 s by default.
 *
 * Not a bound on how long a request may take: a 500 MiB download is
 * expected to run for as long as it runs.  The clock is suspended
 * whenever *we* are the reason nothing is moving, i.e. while we hold
 * libcurl's receive side paused because the client has not drained
 * what we already have.
 */
extern struct GNUNET_TIME_Relative PH_upstream_stall_timeout;


#endif
