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