summaryrefslogtreecommitdiff
path: root/redfish-core/include/query.hpp
blob: e68b89ae0028edda6fee902cc0458b16178bc5a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#pragma once

#include "bmcweb_config.h"

#include "app.hpp"
#include "async_resp.hpp"
#include "error_messages.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "logging.hpp"
#include "utils/query_param.hpp"

#include <boost/beast/http/verb.hpp>
#include <boost/url/params_view.hpp>
#include <boost/url/url_view.hpp>

#include <functional>
#include <memory>
#include <new>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>

// IWYU pragma: no_forward_declare crow::App
// IWYU pragma: no_include <boost/url/impl/params_view.hpp>
// IWYU pragma: no_include <boost/url/impl/url_view.hpp>

#include "redfish_aggregator.hpp"

namespace redfish
{
inline void
    afterIfMatchRequest(crow::App& app,
                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                        crow::Request& req, const std::string& ifMatchHeader,
                        const crow::Response& resIn)
{
    std::string computedEtag = resIn.computeEtag();
    BMCWEB_LOG_DEBUG("User provided if-match etag {} computed etag {}",
                     ifMatchHeader, computedEtag);
    if (computedEtag != ifMatchHeader)
    {
        messages::preconditionFailed(asyncResp->res);
        return;
    }
    // Restart the request without if-match
    req.clearHeader(boost::beast::http::field::if_match);
    BMCWEB_LOG_DEBUG("Restarting request");
    app.handle(req, asyncResp);
}

inline bool handleIfMatch(crow::App& app, const crow::Request& req,
                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
    if (req.session == nullptr)
    {
        // If the user isn't authenticated, don't even attempt to parse match
        // parameters
        return true;
    }

    std::string ifMatch{
        req.getHeaderValue(boost::beast::http::field::if_match)};
    if (ifMatch.empty())
    {
        // No If-Match header.  Nothing to do
        return true;
    }
    if (ifMatch == "*")
    {
        // Representing any resource
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match
        return true;
    }
    if (req.method() != boost::beast::http::verb::patch &&
        req.method() != boost::beast::http::verb::post &&
        req.method() != boost::beast::http::verb::delete_)
    {
        messages::preconditionFailed(asyncResp->res);
        return false;
    }
    boost::system::error_code ec;

    // Try to GET the same resource
    crow::Request newReq(
        {boost::beast::http::verb::get, req.url().encoded_path(), 11}, ec);

    if (ec)
    {
        messages::internalError(asyncResp->res);
        return false;
    }

    // New request has the same credentials as the old request
    newReq.session = req.session;

    // Construct a new response object to fill in, and check the hash of before
    // we modify the Resource.
    std::shared_ptr<bmcweb::AsyncResp> getReqAsyncResp =
        std::make_shared<bmcweb::AsyncResp>();

    getReqAsyncResp->res.setCompleteRequestHandler(std::bind_front(
        afterIfMatchRequest, std::ref(app), asyncResp, req, ifMatch));

    app.handle(newReq, getReqAsyncResp);
    return false;
}

// Sets up the Redfish Route and delegates some of the query parameter
// processing. |queryCapabilities| stores which query parameters will be
// handled by redfish-core/lib codes, then default query parameter handler won't
// process these parameters.
[[nodiscard]] inline bool setUpRedfishRouteWithDelegation(
    crow::App& app, const crow::Request& req,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    query_param::Query& delegated,
    const query_param::QueryCapabilities& queryCapabilities)
{
    BMCWEB_LOG_DEBUG("setup redfish route");

    // Section 7.4 of the redfish spec "Redfish Services shall process the
    // [OData-Version header] in the following table as defined by the HTTP 1.1
    // specification..."
    // Required to pass redfish-protocol-validator REQ_HEADERS_ODATA_VERSION
    std::string_view odataHeader = req.getHeaderValue("OData-Version");
    if (!odataHeader.empty() && odataHeader != "4.0")
    {
        messages::preconditionFailed(asyncResp->res);
        return false;
    }

    asyncResp->res.addHeader("OData-Version", "4.0");

    std::optional<query_param::Query> queryOpt =
        query_param::parseParameters(req.url().params(), asyncResp->res);
    if (!queryOpt)
    {
        return false;
    }

    if (!handleIfMatch(app, req, asyncResp))
    {
        return false;
    }

    bool needToCallHandlers = true;

#ifdef BMCWEB_ENABLE_REDFISH_AGGREGATION
    needToCallHandlers = RedfishAggregator::beginAggregation(req, asyncResp) ==
                         Result::LocalHandle;

    // If the request should be forwarded to a satellite BMC then we don't want
    // to write anything to the asyncResp since it will get overwritten later.
#endif

    // If this isn't a get, no need to do anything with parameters
    if (req.method() != boost::beast::http::verb::get)
    {
        return needToCallHandlers;
    }

    delegated = query_param::delegate(queryCapabilities, *queryOpt);
    std::function<void(crow::Response&)> handler =
        asyncResp->res.releaseCompleteRequestHandler();

    asyncResp->res.setCompleteRequestHandler(
        [&app, handler(std::move(handler)), query{std::move(*queryOpt)},
         delegated{delegated}](crow::Response& resIn) mutable {
        processAllParams(app, query, delegated, handler, resIn);
    });

    return needToCallHandlers;
}

// Sets up the Redfish Route. All parameters are handled by the default handler.
[[nodiscard]] inline bool
    setUpRedfishRoute(crow::App& app, const crow::Request& req,
                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
    // This route |delegated| is never used
    query_param::Query delegated;
    return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated,
                                           query_param::QueryCapabilities{});
}
} // namespace redfish