[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/etr/libhttpserver/master/src/httpserver/hook_context.hpp [Back]  [Original]

/*
     This file is part of libhttpserver
     Copyright (C) 2011-2026 Sebastiano Merlino

     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public
     License as published by the Free Software Foundation; either
     version 2.1 of the License, or (at your option) any later version.

     This library 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
     Lesser General Public License for more details.

     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
     USA
*/

#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
#error "Only  or  can be included directly."
#endif

/**
 * @file hook_context.hpp
 * @brief Per-phase context structs passed to hook callables.
 *
 * Each `*_ctx` type carries exactly the
 * information the matching phase publishes; lifetime is the hook
 * callback invocation -- pointers in these structs MUST NOT be
 * captured past the callback's return.
 */
#ifndef SRC_HTTPSERVER_HOOK_CONTEXT_HPP_
#define SRC_HTTPSERVER_HOOK_CONTEXT_HPP_

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "httpserver/http_method.hpp"

namespace httpserver {

// Forward declarations -- the hook contexts reference http_request and
// http_response only by pointer/reference, so we avoid pulling either
// header into this public surface (no transitive
// backend leak). http_resource is referenced for the route-resolved
// phase via an optional pointer; same forward-decl strategy.
class http_request;
class http_response;
class http_resource;

/**
 * @brief Libhttpserver-defined peer address (IPv4 or IPv6) plus port.
 *
 * Kept libhttpserver-native so the public hook surface carries no
 * `` or MHD types. `bytes` is in
 * network byte order: the first four bytes carry an IPv4 address with
 * the rest zero; all sixteen bytes are used for IPv6. `port` is in
 * host byte order.
 */
struct peer_address {
    enum class family : std::uint8_t { unspec = 0, ipv4 = 1, ipv6 = 2 };
    family fam = family::unspec;
    std::array bytes{};
    std::uint16_t port = 0;

    // Returns a printable representation of the address (no port).
    // Defined out-of-line in src/peer_address.cpp. (Placement note: this
    // struct is logically a context type, but its to_string() body lives in
    // a separate TU to keep both hook_handle.cpp and peer_address.cpp under
    // the per-file LOC ceiling. A future task could consolidate into a
    // dedicated hook_context.cpp if other context types grow out-of-line
    // bodies.)
    [[nodiscard]] std::string to_string() const;
};

/**
 * @brief Light pointer-and-bag view of a matched route.
 *
 * Used by `route_resolved_ctx` and `before_handler_ctx`.
 * `path_template` is a `string_view` into per-request storage; it is
 * valid **only for the duration of the hook call**. Do NOT capture or
 * store the view beyond the hook's return  the backing string is owned
 * by the per-request `connection_context` object and is destroyed when the
 * request completes. If you need the value after the hook returns, copy
 * it into a `std::string` inside the hook body.
 * `methods` carries the method bits the matched entry serves; `is_prefix`
 * flags prefix-match registrations (`register_prefix` / single-resource).
 */
struct route_descriptor {
    std::string_view path_template;
    method_set methods{};
    bool is_prefix = false;
};

// ---- Phase context structs ---------------------------------------------
//
// Each phase has a dedicated context struct so the per-phase add_hook
// overload is distinguishable from the other ten at the type level, and
// so adding fields to one phase does not perturb the others' ABI.
//
// The fields below are deliberately POD-shaped
// (references / scalars / spans / string_views / optionals of POD).

/// @brief Context for the `connection_opened` phase: new TCP/TLS connection.
struct connection_open_ctx {
    peer_address peer{};
};

/// @brief Context for the `connection_closed` phase: connection torn down.
struct connection_close_ctx {
    peer_address peer{};
};

/**
 * @brief Context for the `accept_decision` phase.
 *
 * Observation-only; the handler returns `void`. Accept/deny
 * is decided by the policy callback, not by the hook.
 *
 * @note `accepted` mirrors the policy callback's MHD_YES/MHD_NO return.
 * @note `reason` is set when the connection is rejected:
 *   - `"denied"`  the peer hit the deny list (and is not allow-listed).
 *   - `"not-on-allow-list"`  default policy REJECT and the peer is not
 *     on the allow list.
 *   - `std::nullopt`  the connection was accepted.
 * @note The `string_view`'s referent is a string literal with static
 *   storage duration; capturing it past the hook return is safe. If a
 *   heap-owned copy is needed, use `std::string(*ctx.reason)`.
 */
struct accept_ctx {
    peer_address peer{};
    bool accepted = true;
    std::optional reason{};
};

/**
 * @brief Context for the `request_received` phase.
 *
 * Fires after the `http_request` is fully populated from MHD's
 * headers and BEFORE any body bytes are read. The request pointer is
 * mutable so a hook may adjust per-request state before the upload
 * starts. Short-circuit-capable: returning
 * `hook_action::respond_with(r)` aborts the upload -- the body is
 * never read and the resource handler is never invoked.
 *
 * @security The mutable `request` pointer gives hooks FULL write access
 * to the live request object (headers, method, body state). A buggy or
 * compromised hook can tamper with the request in ways that bypass
 * downstream security checks (auth, validation). Prefer an immutable
 * view if the hook only needs to observe, and audit hooks registered
 * for this phase carefully.
 */
struct request_received_ctx {
    http_request* request = nullptr;          // mutable: hook may set context
    std::chrono::steady_clock::time_point received_at{};
};

/**
 * @brief Context for the `body_chunk` phase.
 *
 * Fires once per chunk MHD delivers to the upload callback, BEFORE
 * the bytes are appended to the request body or fed to any in-flight
 * post-processor. Short-circuit-capable.
 *
 * @attention This phase is invoked from arbitrary MHD worker threads
 *   at arbitrary granularity  on slow networks chunks may be a single
 *   byte. Hooks MUST be cheap (no blocking I/O, no per-chunk heap
 *   allocation in the hot path)  a slow hook back-pressures the
 *   connection's upload.
 * @note `chunk` aliases MHD-owned memory; it is only valid for the
 *   duration of the hook call. Copy into owned storage if the data must
 *   outlive this firing.
 * @note `offset` is the number of body bytes already buffered before
 *   this chunk (first firing has `offset==0`, next has
 *   `offset==chunk0.size()`, etc.).
 * @note Short-circuit: returning `hook_action::respond_with(r)` aborts
 *   the upload at the next MHD callback; the resource handler is never
 *   invoked. Any in-flight post-processor is destroyed and its buffer
 *   freed at the short-circuit point.
 */
struct body_chunk_ctx {
    http_request* request = nullptr;
    std::span chunk{};
    std::uint64_t offset = 0;
    // Currently always false: no firing site ever sets it. End-of-body
    // is signalled by MHD's zero-size upload callback, not this flag;
    // it is reserved for future use. Do not rely on it to detect the
    // last chunk.
    bool is_final = false;
};

/**
 * @brief Context for the `route_resolved` phase.
 *
 * Observation-only. `matched` is empty for the 404 path (no route
 * matched); otherwise it describes the matched registration.
 */
struct route_resolved_ctx {
    const http_request* request = nullptr;
    std::optional matched{};
    const http_resource* resource = nullptr;  // nullable; nullptr for lambda routes
};

/**
 * @brief Context for the `before_handler` phase.
 *
 * Short-circuit-capable. Fires after route resolution (the matched
 * resource is known) and BEFORE both `is_allowed` and the resource
 * handler invocation. Returning `hook_action::respond_with(r)` skips
 * both checks and goes straight to response materialisation. Also
 * the phase used by the `method_not_allowed_handler` and
 * `auth_handler` v1 aliases.
 *
 * @note `method` is the wire method decoded by `answer_to_connection`.
 *   The 405-alias hook consults this against
 *   `resource->get_allowed_methods()` to decide whether to
 *   short-circuit with 405 + Allow header.
 * @note `resource` is the resolved `http_resource` pointer; `nullptr`
 *   for lambda-route registrations without a stable `http_resource*`.
 *   The hook fires only for route hits.
 *
 * @security The mutable `request` pointer gives hooks FULL write access
 * to the live request object. This is intentional (e.g., the auth hook
 * may annotate the request), but a buggy or compromised hook can tamper
 * in ways that bypass downstream security checks. Audit hooks
 * registered for this phase carefully.
 */
struct before_handler_ctx {
    http_request* request = nullptr;
    std::optional matched{};
    http_method method = http_method::count_;
    const http_resource* resource = nullptr;
};

/**
 * @brief Context for the `handler_exception` phase.
 *
 * Short-circuit-capable. Fires when an exception escapes the resource
 * handler, before the `internal_error_handler` v1 alias is consulted.
 */
struct handler_exception_ctx {
    const http_request* request = nullptr;
    std::exception_ptr exception{};
    /**
     * Human-readable description of the exception. For `std::exception`
     * throws this is `e.what()`; for non-std throws this is the sentinel
     * string @c "unknown exception".
     *
     * @security This value originates from application exception text and
     * MAY contain internal detail (DB connection strings, file paths,
     * user-supplied input). Hook implementations and internal_error_handler
     * callbacks MUST NOT forward this value into HTTP response bodies
     * without sanitization (CWE-209: Information Exposure Through an Error
     * Message).
     *
     * @note The view is valid only for the synchronous duration of the hook
     * call chain  it aliases the live exception object's storage. Do NOT
     * store it past the hook's return.
     */
    std::string_view message{};
};

/**
 * @brief Context for the `after_handler` phase.
 *
 * Short-circuit-capable; the `response` pointer is mutable so hooks
 * may rewrite headers / status without replacing the body.
 */
struct after_handler_ctx {
    const http_request* request = nullptr;
    http_response* response = nullptr;        // mutable: hook may mutate
};

/**
 * @brief Context for the `response_sent` phase.
 *
 * Observation point fired immediately after `MHD_queue_response` and
 * BEFORE `MHD_destroy_response`. Carries the data users have been
 * asking for (issues #281 and #69): `status`, `bytes_queued`,
 * `elapsed`. The `log_access` v1 alias is wired through this phase.
 *
 * @note `status` is the HTTP status code passed to
 *   `MHD_queue_response`.
 * @note `bytes_queued` is `http_response::body_->size()`. For deferred
 *   or pipe bodies `size()` returns 0 because the final length is not
 *   yet known at queue time; fall back to the Content-Length header for
 *   streamed bodies.
 * @note `elapsed` is `steady_clock::now()` at the fire site minus
 *   `connection_context::start_time` (captured on the first invocation of
 *   `answer_to_connection`). Granularity is nanoseconds.
 * @note `elapsed` is `nanoseconds::zero()` when only the `log_access`
 *   alias slot fires (no `add_hook(response_sent, ...)` hooks registered).
 *   The alias lambda does not read this field; the zero avoids a
 *   gratuitous `steady_clock::now()` call on that code path.
 * @attention The `response` pointer is non-null at the fire site.
 *   Hooks MUST NOT capture it past their return  the `http_response`
 *   is destroyed in `~connection_context` immediately after
 *   `request_completed` fires.
 */
struct response_sent_ctx {
    const http_request* request = nullptr;
    const http_response* response = nullptr;
    int status = 0;
    std::size_t bytes_queued = 0;
    std::chrono::nanoseconds elapsed{};
};

/**
 * @brief Context for the `request_completed` phase.
 *
 * Unconditional final hook. Fires BEFORE the per-request state is
 * destroyed so the ctx pointers remain backed by live storage for the
 * duration of the hook call. Hooks MUST NOT capture `request` or
 * `resp` past their return.
 *
 * @note `resp` is NULLABLE. On early-failure paths (e.g., a
 *   `request_received` hook returning `respond_with(413)`),
 *   `conn->response` is populated and `resp` points into it. On paths
 *   where MHD terminates the request before any response object is
 *   built, `resp` is `nullptr`.
 * @note `succeeded` maps from `MHD_RequestTerminationCode`:
 *   `MHD_REQUEST_TERMINATED_COMPLETED_OK`  `true`; everything else 
 *   `false`. A user-policy rejection that produced a complete response
 *   on the wire (e.g., a 413 from a `request_received` short-circuit)
 *   reports `succeeded == true` because MHD drove the request to
 *   ordinary completion.
 * @note `duration` is `steady_clock::now()` at the fire site minus
 *   `connection_context::start_time`; mirrors `response_sent_ctx::elapsed`.
 *   On degenerate paths where `answer_to_connection` never ran (e.g.,
 *   a port scan), `start_time` is epoch and `duration` is set to
 *   `nanoseconds{-1}` as a sentinel so hook authors can distinguish
 *   this case from a real (but very slow) request.
 * @attention Hooks MUST NOT capture `request` or `resp` past their
 *   return  both are destroyed in `~connection_context` immediately after
 *   this fire.
 */
struct request_completed_ctx {
    const http_request* request = nullptr;
    const http_response* resp = nullptr;
    bool succeeded = false;
    std::chrono::steady_clock::duration duration{};
};

}  // namespace httpserver
#endif  // SRC_HTTPSERVER_HOOK_CONTEXT_HPP_

Web Proxy Viewer  |  New URL  |  Original Page