/* 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 #ifndef SRC_HTTPSERVER_HOOK_HANDLE_HPP_ #define SRC_HTTPSERVER_HOOK_HANDLE_HPP_ #include #include #include #include "httpserver/hook_phase.hpp" /** * @file hook_handle.hpp * @brief Move-only RAII receipt returned by webserver / http_resource * `add_hook` calls. * * The PIMPL backing classes are forward- * declared so this header carries no libmicrohttpd / pthread / gnutls * baggage. */ namespace httpserver { namespace detail { class webserver_impl; } // Per-resource hook table -- the per-route handle holds a // weak_ptr, expired when the resource is // destroyed (remove() then becomes a safe no-op). namespace detail { class resource_hook_table; } class webserver; class http_resource; /** * @brief Move-only RAII receipt for a hook registration. * * Returned by `webserver::add_hook` (server-wide) or * `http_resource::add_hook` (per-route). Destruction or explicit * `remove()` re-takes the hook table's writer lock and erases the * registration. `detach()` disarms the destructor so the registration * persists for the webserver's lifetime. * * Back-reference: `(phase, slot_id, impl_ or table_weak_)` where * `slot_id` is a monotonically-increasing 64-bit counter. Reallocation * of the phase vector cannot invalidate this handle: `remove()` walks * the vector linearly looking for `slot_id`; a not-found result is * the idempotent no-op path. * * @warning **Lifetime requirement:** A `hook_handle` produced by * `webserver::add_hook` holds a raw non-owning pointer to the * webserver's implementation object. A non-detached handle **must not * outlive the webserver that produced it**. Destroying an armed * `hook_handle` after its webserver has been destroyed is undefined * behavior (use-after-free). CWE-416. * * Safe patterns: * - Store handles as members of a class that also owns the `webserver`. * - Call `h.detach()` if you want the registration to persist for the * webserver's entire lifetime without the handle. * - Call `h.remove()` explicitly before destroying the webserver. * * Per-route handles (`http_resource::add_hook`) hold a * `weak_ptr` instead of a raw pointer; their * `remove()` is safe even after the resource is destroyed (the * weak_ptr expires and the call becomes a no-op). * * @note **Alias slots are construction-time-only.** The v1-derived * setters on `create_webserver` (`log_access`, * `internal_error_handler`, `not_found_handler`, * `method_not_allowed_handler`, `auth_handler`) wire their slot * exactly once during webserver construction and are immutable * thereafter. To add or remove an observation/intervention point at * runtime, use `webserver::add_hook(phase, ...)` and the returned * `hook_handle`. */ class hook_handle { public: /** * @brief Default-construct a disarmed handle. * * `remove()` is a no-op and the destructor does not touch any * impl. Three paths converge on this state: default ctor, * post-`remove()`, and post-move-from. */ hook_handle() noexcept = default; /// hook_handle is non-copyable. hook_handle(const hook_handle&) = delete; /// hook_handle is non-copy-assignable. hook_handle& operator=(const hook_handle&) = delete; /// Move-construct from @p other, leaving it disarmed. hook_handle(hook_handle&& other) noexcept; /// Move-assign from @p other, leaving it disarmed. hook_handle& operator=(hook_handle&& other) noexcept; /** * @brief Destructor. * * Calls `remove()` unless the handle has been `detach()`-ed, * moved-from, or already `remove()`-ed. */ ~hook_handle(); /** * @brief Erase the registration this handle refers to. * * Idempotent: a second call on the same handle is a no-op. After * this call the handle is disarmed (the destructor will not touch * the impl). Safe to call on a default-constructed or moved-from * handle. */ void remove() noexcept; /** * @brief Disarm the destructor, making the registration permanent. * * The underlying registration persists for the webserver's * lifetime. The source handle is left in the disarmed state. * Conventionally invoked as `std::move(h).detach();`. * * @return an inert (disarmed) receipt carrying the same slot data; * its remove() and destructor are no-ops. It does NOT own * the registration — after detach() nothing does, which is * the point. */ hook_handle detach() && noexcept; private: // Constructor used by webserver::add_hook to build an armed handle // bound to the server-wide hook table. hook_handle(detail::webserver_impl* impl, hook_phase phase, std::uint64_t slot_id) noexcept : impl_(impl), slot_id_(slot_id), phase_(phase), armed_(true) {} // Constructor used by http_resource::add_hook to build an armed // handle bound to a per-resource hook table. The discriminator // between the two handle kinds is impl_: remove() takes the // per-resource path when impl_ == nullptr (this constructor leaves // it null) and the server-wide path otherwise; table_weak_ is only // consulted on the per-resource path. // // The handle holds a weak_ptr so a resource that outlives the handle // is not kept alive by the handle (the handle is non-owning), and a // resource destroyed before the handle expires the weak_ptr cleanly // (lock() returns null, remove() is a no-op). hook_handle(std::weak_ptr table_weak, hook_phase phase, std::uint64_t slot_id) noexcept : slot_id_(slot_id), phase_(phase), armed_(true), table_weak_(std::move(table_weak)) {} detail::webserver_impl* impl_ = nullptr; // non-owning back-ref std::uint64_t slot_id_ = 0; hook_phase phase_ = hook_phase::count_; bool armed_ = false; // Non-empty (has-a-control-block) iff this handle is for a per-route // hook on an http_resource. Empty otherwise. std::weak_ptr table_weak_{}; friend class ::httpserver::webserver; friend class ::httpserver::http_resource; friend class ::httpserver::detail::webserver_impl; }; } // namespace httpserver #endif // SRC_HTTPSERVER_HOOK_HANDLE_HPP_