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