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