Coverage Report

Created: 2026-03-10 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bmcweb/http/http_response.hpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
3
#pragma once
4
#include "http_body.hpp"
5
#include "logging.hpp"
6
#include "utils/hex_utils.hpp"
7
8
#include <fcntl.h>
9
10
#include <boost/beast/core/error.hpp>
11
#include <boost/beast/core/file_base.hpp>
12
#include <boost/beast/http/field.hpp>
13
#include <boost/beast/http/fields.hpp>
14
#include <boost/beast/http/message.hpp>
15
#include <boost/beast/http/status.hpp>
16
#include <boost/url/url_view.hpp>
17
#include <nlohmann/json.hpp>
18
19
#include <cstddef>
20
#include <cstdint>
21
#include <filesystem>
22
#include <functional>
23
#include <optional>
24
#include <string>
25
#include <string_view>
26
#include <utility>
27
28
namespace crow
29
{
30
31
template <typename Adaptor, typename Handler>
32
class Connection;
33
34
namespace http = boost::beast::http;
35
36
enum class OpenCode
37
{
38
    Success,
39
    FileDoesNotExist,
40
    InternalError,
41
};
42
43
struct Response
44
{
45
    template <typename Adaptor, typename Handler>
46
    friend class Connection;
47
48
    http::response<bmcweb::HttpBody> response;
49
50
    nlohmann::json jsonValue;
51
    using fields_type = http::header<false, http::fields>;
52
    fields_type& fields()
53
0
    {
54
0
        return response.base();
55
0
    }
56
57
    const fields_type& fields() const
58
0
    {
59
0
        return response.base();
60
0
    }
61
62
    void addHeader(std::string_view key, std::string_view value)
63
0
    {
64
0
        fields().insert(key, value);
65
0
    }
66
67
    void addHeader(http::field key, std::string_view value)
68
0
    {
69
0
        fields().insert(key, value);
70
0
    }
71
72
    void clearHeader(http::field key)
73
0
    {
74
0
        fields().erase(key);
75
0
    }
76
77
    Response() = default;
78
    Response(Response&& res) noexcept :
79
        response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
80
        requestExpectedEtag(std::move(res.requestExpectedEtag)),
81
        currentOverrideEtag(std::move(res.currentOverrideEtag)),
82
        completed(res.completed)
83
0
    {
84
0
        // See note in operator= move handler for why this is needed.
85
0
        if (!res.completed)
86
0
        {
87
0
            completeRequestHandler = std::move(res.completeRequestHandler);
88
0
            res.completeRequestHandler = nullptr;
89
0
        }
90
0
    }
91
92
    ~Response() = default;
93
94
    Response(const Response&) = delete;
95
    Response& operator=(const Response& r) = delete;
96
97
    Response& operator=(Response&& r) noexcept
98
0
    {
99
0
        BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
100
0
                         logPtr(this), logPtr(&r));
101
0
        if (this == &r)
102
0
        {
103
0
            return *this;
104
0
        }
105
0
        response = std::move(r.response);
106
0
        jsonValue = std::move(r.jsonValue);
107
0
        requestExpectedEtag = std::move(r.requestExpectedEtag);
108
0
        currentOverrideEtag = std::move(r.currentOverrideEtag);
109
0
110
0
        // Only need to move completion handler if not already completed
111
0
        // Note, there are cases where we might move out of a Response object
112
0
        // while in a completion handler for that response object.  This check
113
0
        // is intended to prevent destructing the functor we are currently
114
0
        // executing from in that case.
115
0
        if (!r.completed)
116
0
        {
117
0
            completeRequestHandler = std::move(r.completeRequestHandler);
118
0
            r.completeRequestHandler = nullptr;
119
0
        }
120
0
        else
121
0
        {
122
0
            completeRequestHandler = nullptr;
123
0
        }
124
0
        completed = r.completed;
125
0
        return *this;
126
0
    }
127
128
    void result(unsigned v)
129
0
    {
130
0
        fields().result(v);
131
0
    }
132
133
    void result(http::status v)
134
0
    {
135
0
        fields().result(v);
136
0
    }
137
138
    void copyBody(const Response& res)
139
0
    {
140
0
        response.body() = res.response.body();
141
0
    }
142
143
    http::status result() const
144
0
    {
145
0
        return fields().result();
146
0
    }
147
148
    unsigned resultInt() const
149
0
    {
150
0
        return fields().result_int();
151
0
    }
152
153
    std::string_view reason() const
154
0
    {
155
0
        return fields().reason();
156
0
    }
157
158
    bool isCompleted() const noexcept
159
0
    {
160
0
        return completed;
161
0
    }
162
163
    const std::string* body()
164
0
    {
165
0
        return &response.body().str();
166
0
    }
167
168
    std::string_view getHeaderValue(std::string_view key) const
169
0
    {
170
0
        return fields()[key];
171
0
    }
172
173
    std::string_view getHeaderValue(boost::beast::http::field key) const
174
0
    {
175
0
        return fields()[key];
176
0
    }
177
178
    void keepAlive(bool k)
179
0
    {
180
0
        response.keep_alive(k);
181
0
    }
182
183
    bool keepAlive() const
184
0
    {
185
0
        return response.keep_alive();
186
0
    }
187
188
    std::optional<uint64_t> size()
189
0
    {
190
0
        return response.body().payloadSize();
191
0
    }
192
193
    void preparePayload(const boost::urls::url_view& urlView)
194
0
    {
195
0
        std::optional<uint64_t> pSize = response.body().payloadSize();
196
0
197
0
        using http::status;
198
0
        using http::status_class;
199
0
        using http::to_status_class;
200
0
        bool is1XXReturn = to_status_class(result()) ==
201
0
                           status_class::informational;
202
0
        if (!pSize)
203
0
        {
204
0
            response.chunked(true);
205
0
            return;
206
0
        }
207
0
        response.content_length(*pSize);
208
0
209
0
        if ((*pSize > 0) && (is1XXReturn || result() == status::no_content ||
210
0
                             result() == status::not_modified))
211
0
        {
212
0
            BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
213
0
                                "no-content or not_modified, which aren't "
214
0
                                "allowed to have a body for url : \"{}\"",
215
0
                                logPtr(this), urlView.path());
216
0
            response.content_length(0);
217
0
            return;
218
0
        }
219
0
    }
220
221
    void clear()
222
0
    {
223
0
        BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
224
0
        response.clear();
225
0
        response.body().clear();
226
0
227
0
        jsonValue = nullptr;
228
0
        completed = false;
229
0
        requestExpectedEtag = std::nullopt;
230
0
        currentOverrideEtag = std::nullopt;
231
0
    }
232
233
    void setCurrentOverrideEtag(std::string_view newEtag)
234
0
    {
235
0
        if (currentOverrideEtag)
236
0
        {
237
0
            BMCWEB_LOG_WARNING(
238
0
                "Response override etag was incorrectly set twice");
239
0
        }
240
0
        currentOverrideEtag = newEtag;
241
0
    }
242
243
    std::string getCurrentEtag() const
244
0
    {
245
0
        // Only set etag if this request succeeded
246
0
        if (result() != http::status::ok)
247
0
        {
248
0
            return "";
249
0
        }
250
0
        // and the json response isn't empty
251
0
        if (jsonValue.empty())
252
0
        {
253
0
            return "";
254
0
        }
255
0
256
0
        if (currentOverrideEtag)
257
0
        {
258
0
            return currentOverrideEtag.value();
259
0
        }
260
0
261
0
        size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
262
0
        return std::format("\"{:08X}\"", hashval);
263
0
    }
264
265
    void write(std::string&& bodyPart)
266
0
    {
267
0
        response.body().str() = std::move(bodyPart);
268
0
    }
269
270
    void end()
271
0
    {
272
0
        if (completed)
273
0
        {
274
0
            BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
275
0
            return;
276
0
        }
277
0
        completed = true;
278
0
        BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
279
0
        if (completeRequestHandler)
280
0
        {
281
0
            BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
282
0
            completeRequestHandler(*this);
283
0
        }
284
0
    }
285
286
    void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
287
0
    {
288
0
        BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
289
0
        completeRequestHandler = std::move(handler);
290
0
291
0
        // Now that we have a new completion handler attached, we're no longer
292
0
        // complete
293
0
        completed = false;
294
0
    }
295
296
    std::function<void(Response&)> releaseCompleteRequestHandler()
297
0
    {
298
0
        BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
299
0
                         static_cast<bool>(completeRequestHandler));
300
0
        std::function<void(Response&)> ret = completeRequestHandler;
301
0
        completeRequestHandler = nullptr;
302
0
        completed = true;
303
0
        return ret;
304
0
    }
305
306
    void setResponseEtagAndHandleNotModified()
307
0
    {
308
0
        // Can only hash if we have content that's valid
309
0
        if (jsonValue.empty() || result() != http::status::ok)
310
0
        {
311
0
            return;
312
0
        }
313
0
        std::string hexVal = getCurrentEtag();
314
0
        addHeader(http::field::etag, hexVal);
315
0
        if (requestExpectedEtag && hexVal == *requestExpectedEtag)
316
0
        {
317
0
            jsonValue = nullptr;
318
0
            result(http::status::not_modified);
319
0
        }
320
0
    }
321
322
    std::optional<std::string_view> getExpectedEtag() const
323
0
    {
324
0
        return requestExpectedEtag;
325
0
    }
326
327
    void setExpectedEtag(std::string_view etag)
328
0
    {
329
0
        if (requestExpectedEtag)
330
0
        {
331
0
            BMCWEB_LOG_WARNING(
332
0
                "Request expected etag was incorrectly set twice");
333
0
        }
334
0
        requestExpectedEtag = etag;
335
0
    }
336
337
    OpenCode openFile(
338
        const std::filesystem::path& path,
339
        bmcweb::EncodingType enc = bmcweb::EncodingType::Raw,
340
        bmcweb::CompressionType comp = bmcweb::CompressionType::Raw)
341
0
    {
342
0
        boost::beast::error_code ec;
343
0
        response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
344
0
        response.body().encodingType = enc;
345
0
        response.body().compressionType = comp;
346
0
        if (ec)
347
0
        {
348
0
            BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(),
349
0
                             ec.value());
350
0
            if (ec.value() == boost::system::errc::no_such_file_or_directory)
351
0
            {
352
0
                return OpenCode::FileDoesNotExist;
353
0
            }
354
0
            return OpenCode::InternalError;
355
0
        }
356
0
        return OpenCode::Success;
357
0
    }
358
359
    bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
360
0
    {
361
0
        boost::beast::error_code ec;
362
0
        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
363
0
        int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
364
0
        if (retval == -1)
365
0
        {
366
0
            BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed");
367
0
        }
368
0
        response.body().encodingType = enc;
369
0
        response.body().setFd(fd, ec);
370
0
        if (ec)
371
0
        {
372
0
            BMCWEB_LOG_ERROR("Failed to set fd");
373
0
            return false;
374
0
        }
375
0
        return true;
376
0
    }
377
378
  private:
379
    std::optional<std::string> requestExpectedEtag;
380
    std::optional<std::string> currentOverrideEtag;
381
    bool completed = false;
382
    std::function<void(Response&)> completeRequestHandler;
383
};
384
} // namespace crow