1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "error_messages.hpp"
8 #include "http_request.hpp"
9 #include "http_response.hpp"
10 #include "human_sort.hpp"
11 #include "logging.hpp"
12 #include "query.hpp"
13 #include "registries/privilege_registry.hpp"
14 #include "str_utility.hpp"
15
16 #include <boost/beast/http/field.hpp>
17 #include <boost/beast/http/status.hpp>
18 #include <boost/beast/http/verb.hpp>
19 #include <boost/url/format.hpp>
20
21 #include <algorithm>
22 #include <array>
23 #include <filesystem>
24 #include <format>
25 #include <functional>
26 #include <memory>
27 #include <ranges>
28 #include <string>
29 #include <system_error>
30 #include <utility>
31 #include <vector>
32
33 namespace redfish
34 {
35
redfishGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)36 inline void redfishGet(App& app, const crow::Request& req,
37 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
38 {
39 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
40 {
41 return;
42 }
43 asyncResp->res.jsonValue["v1"] = "/redfish/v1/";
44 }
45
redfish404(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & path)46 inline void redfish404(App& app, const crow::Request& req,
47 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
48 const std::string& path)
49 {
50 asyncResp->res.addHeader(boost::beast::http::field::allow, "");
51
52 // If we fall to this route, we didn't have a more specific route, so return
53 // 404
54 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
55 {
56 return;
57 }
58
59 BMCWEB_LOG_WARNING("404 on path {}", path);
60
61 std::string name = req.url().segments().back();
62 // Note, if we hit the wildcard route, we don't know the "type" the user was
63 // actually requesting, but giving them a return with an empty string is
64 // still better than nothing.
65 messages::resourceNotFound(asyncResp->res, "", name);
66 }
67
redfish405(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & path)68 inline void redfish405(App& app, const crow::Request& req,
69 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
70 const std::string& path)
71 {
72 // If we fall to this route, we didn't have a more specific route, so return
73 // 405
74 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
75 {
76 return;
77 }
78
79 BMCWEB_LOG_WARNING("405 on path {}", path);
80 asyncResp->res.result(boost::beast::http::status::method_not_allowed);
81 if (req.method() == boost::beast::http::verb::delete_)
82 {
83 messages::resourceCannotBeDeleted(asyncResp->res);
84 }
85 else
86 {
87 messages::operationNotAllowed(asyncResp->res);
88 }
89 }
90
jsonSchemaIndexGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)91 inline void jsonSchemaIndexGet(
92 App& app, const crow::Request& req,
93 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
94 {
95 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
96 {
97 return;
98 }
99 nlohmann::json& json = asyncResp->res.jsonValue;
100 json["@odata.id"] = "/redfish/v1/JsonSchemas";
101 json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection";
102 json["Name"] = "JsonSchemaFile Collection";
103 json["Description"] = "Collection of JsonSchemaFiles";
104
105 std::error_code ec;
106 std::filesystem::directory_iterator dirList(
107 "/usr/share/www/redfish/v1/JsonSchemas", ec);
108 if (ec)
109 {
110 messages::internalError(asyncResp->res);
111 return;
112 }
113
114 std::vector<std::string> schemaNames;
115 for (const std::filesystem::path& file : dirList)
116 {
117 std::string filename = file.filename();
118 std::vector<std::string> split;
119 bmcweb::split(split, filename, '.');
120 if (split.empty())
121 {
122 continue;
123 }
124 schemaNames.emplace_back(split[0]);
125 }
126 std::ranges::sort(schemaNames, AlphanumLess<std::string>());
127
128 nlohmann::json::array_t members;
129 for (const std::string& schema : schemaNames)
130 {
131 nlohmann::json::object_t member;
132 member["@odata.id"] =
133 boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
134 members.emplace_back(std::move(member));
135 }
136 json["Members@odata.count"] = members.size();
137 json["Members"] = std::move(members);
138 }
139
jsonSchemaGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & schema)140 inline void jsonSchemaGet(App& app, const crow::Request& req,
141 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
142 const std::string& schema)
143 {
144 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
145 {
146 return;
147 }
148
149 std::error_code ec;
150 std::filesystem::directory_iterator dirList(
151 "/usr/share/www/redfish/v1/JsonSchemas", ec);
152 if (ec)
153 {
154 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
155 return;
156 }
157 for (const std::filesystem::path& file : dirList)
158 {
159 std::string filename = file.filename();
160 std::vector<std::string> split;
161 bmcweb::split(split, filename, '.');
162 if (split.empty())
163 {
164 continue;
165 }
166 BMCWEB_LOG_DEBUG("Checking {}", split[0]);
167 if (split[0] != schema)
168 {
169 continue;
170 }
171
172 nlohmann::json& json = asyncResp->res.jsonValue;
173 json["@odata.id"] =
174 boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
175 json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile";
176 json["Name"] = schema + " Schema File";
177 json["Description"] = schema + " Schema File Location";
178 json["Id"] = schema;
179 std::string schemaName = std::format("#{}.{}", schema, schema);
180 json["Schema"] = std::move(schemaName);
181 constexpr std::array<std::string_view, 1> languages{"en"};
182 json["Languages"] = languages;
183 json["Languages@odata.count"] = languages.size();
184
185 nlohmann::json::array_t locationArray;
186 nlohmann::json::object_t locationEntry;
187 locationEntry["Language"] = "en";
188
189 locationEntry["PublicationUri"] = boost::urls::format(
190 "http://redfish.dmtf.org/schemas/v1/{}", filename);
191 locationEntry["Uri"] = boost::urls::format(
192 "/redfish/v1/JsonSchemas/{}/{}", schema, filename);
193
194 locationArray.emplace_back(locationEntry);
195
196 json["Location"] = std::move(locationArray);
197 json["Location@odata.count"] = 1;
198 return;
199 }
200 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
201 }
202
jsonSchemaGetFile(const crow::Request &,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & schema,const std::string & schemaFile)203 inline void jsonSchemaGetFile(
204 const crow::Request& /*req*/,
205 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
206 const std::string& schema, const std::string& schemaFile)
207 {
208 // Sanity check the filename
209 if (schemaFile.find_first_not_of(
210 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") !=
211 std::string::npos)
212 {
213 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
214 return;
215 }
216 // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json
217 // Make sure the two paths match.
218 if (!schemaFile.starts_with(schema))
219 {
220 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
221 return;
222 }
223 std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas");
224 filepath /= schemaFile;
225 if (filepath.is_relative())
226 {
227 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
228 return;
229 }
230
231 crow::OpenCode ec = asyncResp->res.openFile(filepath);
232 if (ec == crow::OpenCode::FileDoesNotExist)
233 {
234 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
235 return;
236 }
237 if (ec == crow::OpenCode::InternalError)
238 {
239 BMCWEB_LOG_DEBUG("failed to read file");
240 messages::internalError(asyncResp->res);
241 return;
242 }
243 }
244
requestRoutesRedfish(App & app)245 inline void requestRoutesRedfish(App& app)
246 {
247 BMCWEB_ROUTE(app, "/redfish/")
248 .methods(boost::beast::http::verb::get)(
249 std::bind_front(redfishGet, std::ref(app)));
250
251 BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>")
252 .privileges(redfish::privileges::getJsonSchemaFile)
253 .methods(boost::beast::http::verb::get)(jsonSchemaGetFile);
254
255 BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/")
256 .privileges(redfish::privileges::getJsonSchemaFileCollection)
257 .methods(boost::beast::http::verb::get)(
258 std::bind_front(jsonSchemaGet, std::ref(app)));
259
260 BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/")
261 .privileges(redfish::privileges::getJsonSchemaFile)
262 .methods(boost::beast::http::verb::get)(
263 std::bind_front(jsonSchemaIndexGet, std::ref(app)));
264
265 // Note, this route must always be registered last
266 BMCWEB_ROUTE(app, "/redfish/<path>")
267 .notFound()
268 .privileges(redfish::privileges::privilegeSetLogin)(
269 std::bind_front(redfish404, std::ref(app)));
270
271 BMCWEB_ROUTE(app, "/redfish/<path>")
272 .methodNotAllowed()
273 .privileges(redfish::privileges::privilegeSetLogin)(
274 std::bind_front(redfish405, std::ref(app)));
275 }
276
277 } // namespace redfish
278