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 
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 
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 
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
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 
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
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 
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