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