|
| 1 | +/* |
| 2 | + This file is part of libhttpserver |
| 3 | + Copyright (C) 2011-2019 Sebastiano Merlino |
| 4 | +
|
| 5 | + This library is free software; you can redistribute it and/or |
| 6 | + modify it under the terms of the GNU Lesser General Public |
| 7 | + License as published by the Free Software Foundation; either |
| 8 | + version 2.1 of the License, or (at your option) any later version. |
| 9 | +
|
| 10 | + This library is distributed in the hope that it will be useful, |
| 11 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | + Lesser General Public License for more details. |
| 14 | +
|
| 15 | + You should have received a copy of the GNU Lesser General Public |
| 16 | + License along with this library; if not, write to the Free Software |
| 17 | + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 |
| 18 | + USA |
| 19 | +*/ |
| 20 | + |
| 21 | +#include "httpserver/details/body.hpp" |
| 22 | + |
| 23 | +#include <fcntl.h> |
| 24 | +#include <sys/stat.h> |
| 25 | +#include <sys/types.h> |
| 26 | +#include <sys/uio.h> |
| 27 | +#include <unistd.h> |
| 28 | + |
| 29 | +#include <cstddef> |
| 30 | +#include <cstdint> |
| 31 | +#include <limits> |
| 32 | +#include <type_traits> |
| 33 | +#include <utility> |
| 34 | + |
| 35 | +#include <microhttpd.h> |
| 36 | + |
| 37 | +namespace httpserver { |
| 38 | + |
| 39 | +namespace detail { |
| 40 | + |
| 41 | +// --------------------------------------------------------------------------- |
| 42 | +// Layout-pinning static_asserts for iovec_entry → MHD_IoVec / struct iovec. |
| 43 | +// Duplicated from src/iovec_response.cpp during the M2 transition: the |
| 44 | +// asserts must live next to every cast site, and TASK-013 will delete |
| 45 | +// iovec_response.cpp once http_response::iovec() lands. Duplicate |
| 46 | +// static_asserts on identical layouts are harmless. |
| 47 | +// |
| 48 | +// LIBHTTPSERVER_TODO_TASK013: drop the originals from iovec_response.cpp |
| 49 | +// when iovec_response is removed. |
| 50 | +// --------------------------------------------------------------------------- |
| 51 | +static_assert(sizeof(::httpserver::iovec_entry) == sizeof(struct iovec), |
| 52 | + "iovec_entry size must match POSIX struct iovec — divergent platform; " |
| 53 | + "implement memcpy fallback (see TASK-004)"); |
| 54 | +static_assert(offsetof(::httpserver::iovec_entry, base) == |
| 55 | + offsetof(struct iovec, iov_base), |
| 56 | + "iovec_entry::base offset must match struct iovec::iov_base"); |
| 57 | +static_assert(offsetof(::httpserver::iovec_entry, len) == |
| 58 | + offsetof(struct iovec, iov_len), |
| 59 | + "iovec_entry::len offset must match struct iovec::iov_len"); |
| 60 | + |
| 61 | +static_assert(sizeof(::httpserver::iovec_entry) == sizeof(MHD_IoVec), |
| 62 | + "iovec_entry size must match libmicrohttpd MHD_IoVec — MHD layout drift"); |
| 63 | +static_assert(offsetof(::httpserver::iovec_entry, base) == |
| 64 | + offsetof(MHD_IoVec, iov_base), |
| 65 | + "iovec_entry::base offset must match MHD_IoVec::iov_base"); |
| 66 | +static_assert(offsetof(::httpserver::iovec_entry, len) == |
| 67 | + offsetof(MHD_IoVec, iov_len), |
| 68 | + "iovec_entry::len offset must match MHD_IoVec::iov_len"); |
| 69 | + |
| 70 | +static_assert(alignof(::httpserver::iovec_entry) == alignof(struct iovec), |
| 71 | + "iovec_entry alignment must match POSIX struct iovec — divergent platform; " |
| 72 | + "implement memcpy fallback (see TASK-004)"); |
| 73 | +static_assert(alignof(::httpserver::iovec_entry) == alignof(MHD_IoVec), |
| 74 | + "iovec_entry alignment must match MHD_IoVec — MHD layout drift"); |
| 75 | + |
| 76 | +static_assert(std::is_standard_layout_v<::httpserver::iovec_entry>, |
| 77 | + "iovec_entry must be standard layout for reinterpret_cast to MHD_IoVec"); |
| 78 | + |
| 79 | +// --------------------------------------------------------------------------- |
| 80 | +// body — virtual destructor anchor (forces vtable emission in this TU). |
| 81 | +// --------------------------------------------------------------------------- |
| 82 | +body::~body() = default; |
| 83 | + |
| 84 | +// --------------------------------------------------------------------------- |
| 85 | +// empty_body |
| 86 | +// --------------------------------------------------------------------------- |
| 87 | +MHD_Response* empty_body::materialize() { |
| 88 | + return MHD_create_response_empty(static_cast<MHD_ResponseFlags>(flags_)); |
| 89 | +} |
| 90 | + |
| 91 | +// --------------------------------------------------------------------------- |
| 92 | +// string_body |
| 93 | +// --------------------------------------------------------------------------- |
| 94 | +MHD_Response* string_body::materialize() { |
| 95 | + // PERSISTENT, not MUST_COPY: content_ is owned by *this and outlives the |
| 96 | + // returned MHD_Response (TASK-009 anchors the lifetime). This matches v1 |
| 97 | + // string_response::get_raw_response. |
| 98 | + return MHD_create_response_from_buffer( |
| 99 | + content_.size(), |
| 100 | + const_cast<void*>(static_cast<const void*>(content_.data())), |
| 101 | + MHD_RESPMEM_PERSISTENT); |
| 102 | +} |
| 103 | + |
| 104 | +// --------------------------------------------------------------------------- |
| 105 | +// file_body — opens the file and fstat's it at construction so size() is |
| 106 | +// accurate immediately. materialize() uses fstat's st_size; it never calls |
| 107 | +// lseek(), so the fd's read position remains at 0 when handed to |
| 108 | +// MHD_create_response_from_fd (security-reviewer-iter1-1 / CWE-367). |
| 109 | +// --------------------------------------------------------------------------- |
| 110 | +file_body::file_body(std::string path) noexcept |
| 111 | + : path_(std::move(path)) { |
| 112 | +#ifndef _WIN32 |
| 113 | + fd_ = ::open(path_.c_str(), O_RDONLY | O_NOFOLLOW); |
| 114 | +#else |
| 115 | + fd_ = ::open(path_.c_str(), O_RDONLY); |
| 116 | +#endif |
| 117 | + if (fd_ == -1) return; |
| 118 | + |
| 119 | + struct stat sb; |
| 120 | + if (::fstat(fd_, &sb) != 0 || !S_ISREG(sb.st_mode)) { |
| 121 | + ::close(fd_); |
| 122 | + fd_ = -1; |
| 123 | + return; |
| 124 | + } |
| 125 | + |
| 126 | + // Use fstat's st_size directly — no lseek, no TOCTOU, no fd-position |
| 127 | + // side-effect (security-reviewer-iter1-1 / performance-reviewer-iter1-4). |
| 128 | + size_ = static_cast<std::size_t>(sb.st_size); |
| 129 | +} |
| 130 | + |
| 131 | +file_body::~file_body() { |
| 132 | + // Close only if MHD never took ownership (materialized_ stays false until |
| 133 | + // MHD_create_response_from_fd returns non-null). |
| 134 | + if (!materialized_ && fd_ != -1) { |
| 135 | + ::close(fd_); |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +MHD_Response* file_body::materialize() { |
| 140 | + if (fd_ == -1) return nullptr; |
| 141 | + |
| 142 | + if (size_) { |
| 143 | + MHD_Response* r = MHD_create_response_from_fd(size_, fd_); |
| 144 | + if (r != nullptr) { |
| 145 | + materialized_ = true; // MHD now owns fd_ |
| 146 | + } |
| 147 | + return r; |
| 148 | + } |
| 149 | + // Zero-byte file: serve empty response without giving the fd to MHD. |
| 150 | + ::close(fd_); |
| 151 | + fd_ = -1; |
| 152 | + materialized_ = true; // suppress ~file_body's close (already closed) |
| 153 | + return MHD_create_response_from_buffer( |
| 154 | + 0, nullptr, MHD_RESPMEM_PERSISTENT); |
| 155 | +} |
| 156 | + |
| 157 | +// --------------------------------------------------------------------------- |
| 158 | +// iovec_body |
| 159 | +// --------------------------------------------------------------------------- |
| 160 | +MHD_Response* iovec_body::materialize() { |
| 161 | + // CWE-190 guard preserved from v1 iovec_response::get_raw_response. |
| 162 | + if (entries_.size() > |
| 163 | + static_cast<std::size_t>( |
| 164 | + std::numeric_limits<unsigned int>::max())) { |
| 165 | + return nullptr; |
| 166 | + } |
| 167 | + return MHD_create_response_from_iovec( |
| 168 | + reinterpret_cast<const MHD_IoVec*>(entries_.data()), |
| 169 | + static_cast<unsigned int>(entries_.size()), |
| 170 | + nullptr, |
| 171 | + nullptr); |
| 172 | +} |
| 173 | + |
| 174 | +// --------------------------------------------------------------------------- |
| 175 | +// pipe_body |
| 176 | +// --------------------------------------------------------------------------- |
| 177 | +pipe_body::~pipe_body() { |
| 178 | + // Only close if MHD never took ownership. After a successful |
| 179 | + // materialize(), libmicrohttpd closes fd_ when the MHD_Response is |
| 180 | + // destroyed. |
| 181 | + if (!materialized_ && fd_ != -1) { |
| 182 | + ::close(fd_); |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +MHD_Response* pipe_body::materialize() { |
| 187 | + MHD_Response* r = MHD_create_response_from_pipe(fd_); |
| 188 | + if (r != nullptr) { |
| 189 | + materialized_ = true; // MHD now owns fd_ |
| 190 | + } |
| 191 | + return r; |
| 192 | +} |
| 193 | + |
| 194 | +// --------------------------------------------------------------------------- |
| 195 | +// deferred_body — trampoline + materialize. |
| 196 | +// --------------------------------------------------------------------------- |
| 197 | +ssize_t deferred_body::trampoline(void* cls, std::uint64_t pos, |
| 198 | + char* buf, std::size_t max) { |
| 199 | + // Guard against null cls or empty producer_ (security-reviewer-iter1-3 / |
| 200 | + // CWE-476). MHD's callback mechanism does not catch C++ exceptions, so |
| 201 | + // throwing std::bad_function_call here would call std::terminate(). |
| 202 | + // Return MHD_CONTENT_READER_END_WITH_ERROR instead. |
| 203 | + auto* self = static_cast<deferred_body*>(cls); |
| 204 | + if (!self || !self->producer_) { |
| 205 | + return MHD_CONTENT_READER_END_WITH_ERROR; |
| 206 | + } |
| 207 | + return self->producer_(pos, buf, max); |
| 208 | +} |
| 209 | + |
| 210 | +MHD_Response* deferred_body::materialize() { |
| 211 | + // Block size 1024 mirrors v1 deferred_response::get_raw_response_helper. |
| 212 | + // Free-callback is nullptr because *this owns producer_ and outlives the |
| 213 | + // MHD_Response (TASK-009 enforces this via http_response's lifetime). |
| 214 | + return MHD_create_response_from_callback( |
| 215 | + MHD_SIZE_UNKNOWN, 1024, &deferred_body::trampoline, this, nullptr); |
| 216 | +} |
| 217 | + |
| 218 | +} // namespace detail |
| 219 | + |
| 220 | +} // namespace httpserver |
0 commit comments