/*
     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_helper.h
 * @brief helper functions
 */
#ifndef PAIVANA_HTTPD_HELPER_H
#define PAIVANA_HTTPD_HELPER_H

#include <stdbool.h>
#include <microhttpd.h>
#include <gnunet/gnunet_util_lib.h>


/**
 * Longest forwarding chain we are willing to look at.
 *
 * A real chain has single digits; MHD's default connection pool would
 * otherwise let a client hand us some 2500 elements per request, and
 * anything that touches each of them is work we do on the one thread
 * that also drives all outbound traffic.  A longer chain is treated
 * as malformed, i.e. as if no forwarding header had been sent.
 */
#define PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS 32


/**
 * Everything about a request that the forwarding layer may look at.
 *
 * All header members are arrays of the *field line* values MHD
 * reported for that field name, in the order they arrived, terminated
 * by a NULL entry (NULL for "field not present at all").  RFC 9110
 * §5.3 makes repeated field lines of a list-valued field one list, so
 * every line has to be considered, not just the first.
 *
 * `Forwarded` and `X-Forwarded-For` say the same thing in two
 * grammars, so only one of them is walked: `Forwarded` wherever it is
 * present at all, `X-Forwarded-For` otherwise.  The two are never
 * merged -- a proxy that emits both and disagrees with itself would
 * otherwise make the answer depend on the order we happened to read
 * them in.
 */
struct PAIVANA_HTTPD_Forwarding
{

  /**
   * Address of the peer we accepted the connection from, in the binary
   * form used throughout, or NULL for a peer that has none (AF_UNIX).
   */
  const void *peer;

  /**
   * Number of bytes in @e peer.
   */
  size_t peer_len;

  /**
   * Are we behind a trusted reverse proxy, i.e. was `-f` given?  If
   * not, none of the header members below is looked at at all.
   */
  bool respect_forwarded;

  /**
   * Field lines of the `Forwarded` header (RFC 7239).
   */
  const char *const *forwarded;

  /**
   * Field lines of the `X-Forwarded-For` header.
   */
  const char *const *xff;

  /**
   * Field lines of the `X-Forwarded-Proto` header.
   */
  const char *const *xfp;

  /**
   * Field lines of the `X-Forwarded-Host` header.
   */
  const char *const *xfh;

  /**
   * Field lines of the `X-Forwarded-Port` header.
   */
  const char *const *xfport;
};


/**
 * What the forwarding layer concluded about the client.
 */
struct PAIVANA_HTTPD_Client
{

  /**
   * Client address in binary form: 4 bytes (`struct in_addr`) for
   * IPv4, 16 bytes (`struct in6_addr`) for IPv6.  NULL only if the
   * peer had no address and no header supplied one.
   */
  void *ca;

  /**
   * Number of bytes in @e ca.
   */
  size_t ca_len;

  /**
   * Scheme the client itself used, "http" or "https", or NULL if
   * nothing we trust said.
   */
  char *proto;

  /**
   * Authority the client itself addressed (host, optionally with a
   * port), or NULL if nothing we trust said.
   */
  char *host;
};


/**
 * Determine who the client is, and what it thinks it connected to.
 *
 * This is the one walk over the forwarding chain; both the address the
 * access cookie is keyed on and the base URL we reconstruct come out
 * of it, so that the two can never disagree about which hop was
 * believed.
 *
 * Given `-f` we are behind a trusted reverse proxy, so the peer we
 * accepted from is trusted implicitly and `TRUSTED_PROXIES` names the
 * *additional* hops further out that may also speak for their
 * predecessor.  The chain is therefore walked from the right: we step
 * leftwards over a node for as long as that node is one we trust, and
 * the first node that is not is the client.  Nothing to the left of an
 * untrusted node is attributable to anyone, and nothing to the left of
 * a node that names no address (RFC 7239 §6.3 `unknown`, an obfuscated
 * identifier, a hostname) can be reached at all.
 *
 * The scheme and authority are taken from the same element the address
 * was: that element was written by a hop we trust and describes the
 * connection its predecessor -- the client -- made.  Where the chain
 * says nothing, the de-facto `X-Forwarded-Proto` / `-Host` / `-Port`
 * headers are consulted instead, and they too only when `-f` says
 * something in front of us is entitled to have set them.
 *
 * Anything the client controls that we cannot believe falls back to
 * the peer: a malformed, empty or over-long chain never removes the
 * one address we know for certain.
 *
 * @param fi what the request carried
 * @param[out] cl what we concluded; to be released with
 *   #PAIVANA_HTTPD_client_clear() whatever the return value
 * @return true if a client address was determined, false only if the
 *   peer has no address and no header supplied one
 */
bool
PAIVANA_HTTPD_resolve_forwarding (const struct PAIVANA_HTTPD_Forwarding *fi,
                                  struct PAIVANA_HTTPD_Client *cl);


/**
 * Release the members of @a cl and zero it.
 *
 * @param[in,out] cl result of #PAIVANA_HTTPD_resolve_forwarding()
 */
void
PAIVANA_HTTPD_client_clear (struct PAIVANA_HTTPD_Client *cl);


/**
 * Obtain the client address of @a connection.
 *
 * A thin adapter over #PAIVANA_HTTPD_resolve_forwarding(): it reads
 * the socket peer and the forwarding field lines off @a connection and
 * hands them to that function, which is where the policy lives.
 *
 * The address is returned in binary form: 4 bytes (`struct in_addr`)
 * for IPv4, 16 bytes (`struct in6_addr`) for IPv6.  That
 * representation is canonical and does not depend on where the
 * address was taken from -- a forwarding header or the socket -- which
 * matters because it feeds the access-cookie MAC: two requests from
 * the same host have to yield the same bytes or the cookie stops
 * verifying.
 *
 * @param connection HTTP client connection
 * @param[out] ca where to write the client address
 * @param[out] ca_len number of bytes in @a ca
 * @return true on success, false if no address could be determined,
 *   which for a request that arrived over IP cannot happen: only a
 *   non-IP socket with nothing to fall back on gets here
 */
bool
PAIVANA_HTTPD_get_client_address (struct MHD_Connection *connection,
                                  void **ca,
                                  size_t *ca_len);


/**
 * Render the `for=` chain of a `Forwarded` header value in
 * `X-Forwarded-For` form, so that an origin which speaks only the
 * de-facto header still learns the client.
 *
 * Returns NULL unless *every* element carries a bare IP address:
 * `X-Forwarded-For` has no way to say "this hop had no address", so an
 * element whose `for` is RFC 7239's `unknown` or an obfuscated
 * identifier cannot be represented, and omitting it would silently
 * shift every position to its left.
 *
 * @param fwd value of the `Forwarded` header
 * @return the comma-separated chain, or NULL; to be freed by the
 *   caller
 */
char *
PAIVANA_HTTPD_forwarded_for_chain (const char *fwd);


/**
 * Read a parameter of the leftmost element of a `Forwarded` header
 * value.  Only `proto` and `host` are supported.
 *
 * The leftmost element describes the connection the client itself
 * made, which is what those two are being asked about.
 *
 * The result is *validated*, not merely unquoted: `proto` is one of
 * the two literals "http" and "https", and `host` is an RFC 3986 §3.2
 * authority.  Neither can therefore contain a `;`, `,`, `"` or space,
 * which is what makes it safe to splice into a header we build.  A
 * value that does not pass is reported as absent.
 *
 * Note that a `host` may still contain `:` and `[`/`]`, which are not
 * `tchar` (RFC 9110 §5.6.2): pass it through
 * #PAIVANA_HTTPD_forwarded_value() before emitting it inside a
 * `Forwarded` element, though it may be used as-is as the value of an
 * `X-Forwarded-Host`.
 *
 * @param fwd value of the `Forwarded` header
 * @param name parameter to look for, "proto" or "host"
 * @return the value, or NULL if the header does not carry a usable
 *   one; to be freed by the caller
 */
char *
PAIVANA_HTTPD_forwarded_param (const char *fwd,
                               const char *name);


/**
 * Render @a v as an RFC 7239 §4 `value`, i.e. as a bare token where
 * that is legal and as an escaped quoted-string otherwise.
 *
 * Splicing a value into a `Forwarded` element without this is how a
 * `;` or `,` in it becomes a parameter or an element of its own, and
 * an origin that trusts us reads what the client wrote as something we
 * said.
 *
 * @param v value to render
 * @return the token or quoted-string, or NULL if @a v contains bytes
 *   that no `Forwarded` value may carry (a control character other
 *   than HTAB); to be freed by the caller
 */
char *
PAIVANA_HTTPD_forwarded_value (const char *v);


/**
 * Render @a ca as an RFC 7239 `for=` node identifier, quoting and
 * bracketing an IPv6 address as §6 requires.
 *
 * @param ca address in binary form, or NULL for a peer that has none
 * @param ca_len number of bytes in @a ca
 * @return the identifier, e.g. "203.0.113.7", "\"[2001:db8::1]\"" or
 *   "unknown"; to be freed by the caller
 */
char *
PAIVANA_HTTPD_forwarded_node (const void *ca,
                              size_t ca_len);


/**
 * Is @a ca the address of a reverse proxy we trust to report the
 * client address truthfully, i.e. one covered by `TRUSTED_PROXIES` or
 * `TRUSTED_PROXIES6`?
 *
 * Always false if no policy was configured: "trust nothing in
 * particular" and "trust everything" are deliberately different
 * answers, and only the former can be stated safely by default.
 *
 * @param ca address in the binary form used throughout, i.e. 4 bytes
 *   for IPv4 and 16 for IPv6
 * @param ca_len number of bytes in @a ca
 * @return true if @a ca is within a configured trusted network
 */
bool
PAIVANA_HTTPD_is_trusted_proxy (const void *ca,
                                size_t ca_len);

/**
 * Determine the Base URL that the client made the HTTP request to.
 * The URL returned will be without the trailing '/'.
 *
 * `BASE_URL` wins if it is configured.  Otherwise the scheme and
 * authority come from #PAIVANA_HTTPD_resolve_forwarding(), i.e. from
 * the same element of the forwarding chain the client address came
 * from, so that the string the cookie is keyed on and the address it
 * is keyed on were reported by the same trusted hop.  Where that says
 * nothing we go by the transport and the `Host` header.
 *
 * @param connection client connection used
 * @param[out] buf where to write the base URL; buffer will be cleared
 *   and must be reaped by caller.
 * @return false on error (no suitable HTTP headers found and nothing
 *   configured)
 */
bool
PAIVANA_HTTPD_get_base_url (struct MHD_Connection *connection,
                            struct GNUNET_Buffer *buf);


#endif
