140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 34c25d66eSEd Tanous #pragma once 44c25d66eSEd Tanous 53ccb3adbSEd Tanous #include "app.hpp" 6d7857201SEd Tanous #include "async_resp.hpp" 781d523a7SEd Tanous #include "error_messages.hpp" 83ccb3adbSEd Tanous #include "http_request.hpp" 93ccb3adbSEd Tanous #include "http_response.hpp" 10*3577e446SEd Tanous #include "human_sort.hpp" 11d7857201SEd Tanous #include "logging.hpp" 123ccb3adbSEd Tanous #include "query.hpp" 133ccb3adbSEd Tanous #include "registries/privilege_registry.hpp" 14d7857201SEd Tanous #include "str_utility.hpp" 1581d523a7SEd Tanous 16d7857201SEd Tanous #include <boost/beast/http/field.hpp> 17d7857201SEd Tanous #include <boost/beast/http/status.hpp> 18d7857201SEd Tanous #include <boost/beast/http/verb.hpp> 19ef4c65b7SEd Tanous #include <boost/url/format.hpp> 20ef4c65b7SEd Tanous 21*3577e446SEd Tanous #include <algorithm> 22d7857201SEd Tanous #include <array> 23d7857201SEd Tanous #include <filesystem> 24d7857201SEd Tanous #include <format> 25d7857201SEd Tanous #include <functional> 26d7857201SEd Tanous #include <memory> 273544d2a7SEd Tanous #include <ranges> 284c25d66eSEd Tanous #include <string> 29d7857201SEd Tanous #include <system_error> 30d7857201SEd Tanous #include <utility> 31d7857201SEd Tanous #include <vector> 324c25d66eSEd Tanous 334c25d66eSEd Tanous namespace redfish 344c25d66eSEd Tanous { 354c25d66eSEd Tanous 36d3355c5cSEd Tanous inline void redfishGet(App& app, const crow::Request& req, 37d3355c5cSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 384c25d66eSEd Tanous { 393ba00073SCarson Labrado if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 401e925c84SEd Tanous { 411e925c84SEd Tanous return; 421e925c84SEd Tanous } 431476687dSEd Tanous asyncResp->res.jsonValue["v1"] = "/redfish/v1/"; 44d3355c5cSEd Tanous } 45d3355c5cSEd Tanous 468c623a96SEd Tanous inline void redfish404(App& app, const crow::Request& req, 478c623a96SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 488c623a96SEd Tanous const std::string& path) 498c623a96SEd Tanous { 508c623a96SEd Tanous asyncResp->res.addHeader(boost::beast::http::field::allow, ""); 518c623a96SEd Tanous 528c623a96SEd Tanous // If we fall to this route, we didn't have a more specific route, so return 538c623a96SEd Tanous // 404 54686b7093SNan Zhou if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 558c623a96SEd Tanous { 568c623a96SEd Tanous return; 578c623a96SEd Tanous } 588c623a96SEd Tanous 5962598e31SEd Tanous BMCWEB_LOG_WARNING("404 on path {}", path); 608c623a96SEd Tanous 6139662a3bSEd Tanous std::string name = req.url().segments().back(); 628c623a96SEd Tanous // Note, if we hit the wildcard route, we don't know the "type" the user was 638c623a96SEd Tanous // actually requesting, but giving them a return with an empty string is 648c623a96SEd Tanous // still better than nothing. 65079360aeSEd Tanous messages::resourceNotFound(asyncResp->res, "", name); 668c623a96SEd Tanous } 678c623a96SEd Tanous 6844c70412SEd Tanous inline void redfish405(App& app, const crow::Request& req, 6944c70412SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 7044c70412SEd Tanous const std::string& path) 7144c70412SEd Tanous { 7244c70412SEd Tanous // If we fall to this route, we didn't have a more specific route, so return 7344c70412SEd Tanous // 405 7444c70412SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 7544c70412SEd Tanous { 7644c70412SEd Tanous return; 7744c70412SEd Tanous } 7844c70412SEd Tanous 7962598e31SEd Tanous BMCWEB_LOG_WARNING("405 on path {}", path); 8044c70412SEd Tanous asyncResp->res.result(boost::beast::http::status::method_not_allowed); 8144c70412SEd Tanous if (req.method() == boost::beast::http::verb::delete_) 8244c70412SEd Tanous { 8344c70412SEd Tanous messages::resourceCannotBeDeleted(asyncResp->res); 8444c70412SEd Tanous } 8544c70412SEd Tanous else 8644c70412SEd Tanous { 8744c70412SEd Tanous messages::operationNotAllowed(asyncResp->res); 8844c70412SEd Tanous } 8944c70412SEd Tanous } 9044c70412SEd Tanous 91504af5a0SPatrick Williams inline void jsonSchemaIndexGet( 92504af5a0SPatrick Williams App& app, const crow::Request& req, 9381d523a7SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 9481d523a7SEd Tanous { 9581d523a7SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 9681d523a7SEd Tanous { 9781d523a7SEd Tanous return; 9881d523a7SEd Tanous } 9981d523a7SEd Tanous nlohmann::json& json = asyncResp->res.jsonValue; 10081d523a7SEd Tanous json["@odata.id"] = "/redfish/v1/JsonSchemas"; 10181d523a7SEd Tanous json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection"; 10281d523a7SEd Tanous json["Name"] = "JsonSchemaFile Collection"; 10381d523a7SEd Tanous json["Description"] = "Collection of JsonSchemaFiles"; 104a529a6aaSEd Tanous 105a529a6aaSEd Tanous std::error_code ec; 106a529a6aaSEd Tanous std::filesystem::directory_iterator dirList( 107a529a6aaSEd Tanous "/usr/share/www/redfish/v1/JsonSchemas", ec); 108a529a6aaSEd Tanous if (ec) 10981d523a7SEd Tanous { 110a529a6aaSEd Tanous messages::internalError(asyncResp->res); 111a529a6aaSEd Tanous return; 112a529a6aaSEd Tanous } 11304c39938SMyung Bae 11404c39938SMyung Bae std::vector<std::string> schemaNames; 115a529a6aaSEd Tanous for (const std::filesystem::path& file : dirList) 116a529a6aaSEd Tanous { 117a529a6aaSEd Tanous std::string filename = file.filename(); 118a529a6aaSEd Tanous std::vector<std::string> split; 119a529a6aaSEd Tanous bmcweb::split(split, filename, '.'); 120a529a6aaSEd Tanous if (split.empty()) 121a529a6aaSEd Tanous { 122a529a6aaSEd Tanous continue; 123a529a6aaSEd Tanous } 12404c39938SMyung Bae schemaNames.emplace_back(split[0]); 12504c39938SMyung Bae } 12604c39938SMyung Bae std::ranges::sort(schemaNames, AlphanumLess<std::string>()); 12704c39938SMyung Bae 12804c39938SMyung Bae nlohmann::json::array_t members; 12904c39938SMyung Bae for (const std::string& schema : schemaNames) 13004c39938SMyung Bae { 13181d523a7SEd Tanous nlohmann::json::object_t member; 132bd79bce8SPatrick Williams member["@odata.id"] = 13304c39938SMyung Bae boost::urls::format("/redfish/v1/JsonSchemas/{}", schema); 134ad539545SPatrick Williams members.emplace_back(std::move(member)); 13581d523a7SEd Tanous } 136a529a6aaSEd Tanous json["Members@odata.count"] = members.size(); 13781d523a7SEd Tanous json["Members"] = std::move(members); 13881d523a7SEd Tanous } 13981d523a7SEd Tanous 14081d523a7SEd Tanous inline void jsonSchemaGet(App& app, const crow::Request& req, 14181d523a7SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 14281d523a7SEd Tanous const std::string& schema) 14381d523a7SEd Tanous { 14481d523a7SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 14581d523a7SEd Tanous { 14681d523a7SEd Tanous return; 14781d523a7SEd Tanous } 14881d523a7SEd Tanous 149a529a6aaSEd Tanous std::error_code ec; 150a529a6aaSEd Tanous std::filesystem::directory_iterator dirList( 151a529a6aaSEd Tanous "/usr/share/www/redfish/v1/JsonSchemas", ec); 152a529a6aaSEd Tanous if (ec) 15381d523a7SEd Tanous { 154d8a5d5d8SJiaqing Zhao messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 15581d523a7SEd Tanous return; 15681d523a7SEd Tanous } 157a529a6aaSEd Tanous for (const std::filesystem::path& file : dirList) 158a529a6aaSEd Tanous { 159a529a6aaSEd Tanous std::string filename = file.filename(); 160a529a6aaSEd Tanous std::vector<std::string> split; 161a529a6aaSEd Tanous bmcweb::split(split, filename, '.'); 162a529a6aaSEd Tanous if (split.empty()) 163a529a6aaSEd Tanous { 164a529a6aaSEd Tanous continue; 165a529a6aaSEd Tanous } 166a529a6aaSEd Tanous BMCWEB_LOG_DEBUG("Checking {}", split[0]); 167a529a6aaSEd Tanous if (split[0] != schema) 168a529a6aaSEd Tanous { 169a529a6aaSEd Tanous continue; 170a529a6aaSEd Tanous } 17181d523a7SEd Tanous 17281d523a7SEd Tanous nlohmann::json& json = asyncResp->res.jsonValue; 173bd79bce8SPatrick Williams json["@odata.id"] = 174bd79bce8SPatrick Williams boost::urls::format("/redfish/v1/JsonSchemas/{}", schema); 17581d523a7SEd Tanous json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile"; 17681d523a7SEd Tanous json["Name"] = schema + " Schema File"; 17781d523a7SEd Tanous json["Description"] = schema + " Schema File Location"; 17881d523a7SEd Tanous json["Id"] = schema; 179a529a6aaSEd Tanous std::string schemaName = std::format("#{}.{}", schema, schema); 18081d523a7SEd Tanous json["Schema"] = std::move(schemaName); 18181d523a7SEd Tanous constexpr std::array<std::string_view, 1> languages{"en"}; 18281d523a7SEd Tanous json["Languages"] = languages; 18381d523a7SEd Tanous json["Languages@odata.count"] = languages.size(); 18481d523a7SEd Tanous 18581d523a7SEd Tanous nlohmann::json::array_t locationArray; 18681d523a7SEd Tanous nlohmann::json::object_t locationEntry; 18781d523a7SEd Tanous locationEntry["Language"] = "en"; 188a529a6aaSEd Tanous 189a529a6aaSEd Tanous locationEntry["PublicationUri"] = boost::urls::format( 190a529a6aaSEd Tanous "http://redfish.dmtf.org/schemas/v1/{}", filename); 191ef4c65b7SEd Tanous locationEntry["Uri"] = boost::urls::format( 192a529a6aaSEd Tanous "/redfish/v1/JsonSchemas/{}/{}", schema, filename); 19381d523a7SEd Tanous 19481d523a7SEd Tanous locationArray.emplace_back(locationEntry); 19581d523a7SEd Tanous 19681d523a7SEd Tanous json["Location"] = std::move(locationArray); 19781d523a7SEd Tanous json["Location@odata.count"] = 1; 198a529a6aaSEd Tanous return; 199a529a6aaSEd Tanous } 200a529a6aaSEd Tanous messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 201a529a6aaSEd Tanous } 202a529a6aaSEd Tanous 203504af5a0SPatrick Williams inline void jsonSchemaGetFile( 204504af5a0SPatrick Williams const crow::Request& /*req*/, 205a529a6aaSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 206a529a6aaSEd Tanous const std::string& schema, const std::string& schemaFile) 207a529a6aaSEd Tanous { 208a529a6aaSEd Tanous // Sanity check the filename 209a529a6aaSEd Tanous if (schemaFile.find_first_not_of( 210a529a6aaSEd Tanous "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") != 211a529a6aaSEd Tanous std::string::npos) 212a529a6aaSEd Tanous { 213a529a6aaSEd Tanous messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 214a529a6aaSEd Tanous return; 215a529a6aaSEd Tanous } 216a529a6aaSEd Tanous // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json 217a529a6aaSEd Tanous // Make sure the two paths match. 218a529a6aaSEd Tanous if (!schemaFile.starts_with(schema)) 219a529a6aaSEd Tanous { 220a529a6aaSEd Tanous messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 221a529a6aaSEd Tanous return; 222a529a6aaSEd Tanous } 223a529a6aaSEd Tanous std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas"); 224a529a6aaSEd Tanous filepath /= schemaFile; 225a529a6aaSEd Tanous if (filepath.is_relative()) 226a529a6aaSEd Tanous { 227a529a6aaSEd Tanous messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 228a529a6aaSEd Tanous return; 229a529a6aaSEd Tanous } 230a529a6aaSEd Tanous 231d51c61b4SMyung Bae crow::OpenCode ec = asyncResp->res.openFile(filepath); 232d51c61b4SMyung Bae if (ec == crow::OpenCode::FileDoesNotExist) 233d51c61b4SMyung Bae { 234d51c61b4SMyung Bae messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 235d51c61b4SMyung Bae return; 236d51c61b4SMyung Bae } 237d51c61b4SMyung Bae if (ec == crow::OpenCode::InternalError) 238a529a6aaSEd Tanous { 239a529a6aaSEd Tanous BMCWEB_LOG_DEBUG("failed to read file"); 240d51c61b4SMyung Bae messages::internalError(asyncResp->res); 241a529a6aaSEd Tanous return; 242a529a6aaSEd Tanous } 24381d523a7SEd Tanous } 24481d523a7SEd Tanous 245f65fca6aSEd Tanous inline void requestRoutesRedfish(App& app) 246d3355c5cSEd Tanous { 247d3355c5cSEd Tanous BMCWEB_ROUTE(app, "/redfish/") 248d3355c5cSEd Tanous .methods(boost::beast::http::verb::get)( 249d3355c5cSEd Tanous std::bind_front(redfishGet, std::ref(app))); 2508c623a96SEd Tanous 251a529a6aaSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>") 252a529a6aaSEd Tanous .privileges(redfish::privileges::getJsonSchemaFile) 253a529a6aaSEd Tanous .methods(boost::beast::http::verb::get)(jsonSchemaGetFile); 254a529a6aaSEd Tanous 25581d523a7SEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/") 2560ea4b4e2SEd Tanous .privileges(redfish::privileges::getJsonSchemaFileCollection) 25781d523a7SEd Tanous .methods(boost::beast::http::verb::get)( 25881d523a7SEd Tanous std::bind_front(jsonSchemaGet, std::ref(app))); 25981d523a7SEd Tanous 26081d523a7SEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/") 2610ea4b4e2SEd Tanous .privileges(redfish::privileges::getJsonSchemaFile) 26281d523a7SEd Tanous .methods(boost::beast::http::verb::get)( 26381d523a7SEd Tanous std::bind_front(jsonSchemaIndexGet, std::ref(app))); 264e9dd1d31SEd Tanous 265e9dd1d31SEd Tanous // Note, this route must always be registered last 266e9dd1d31SEd Tanous BMCWEB_ROUTE(app, "/redfish/<path>") 2670ea4b4e2SEd Tanous .notFound() 2680ea4b4e2SEd Tanous .privileges(redfish::privileges::privilegeSetLogin)( 2690ea4b4e2SEd Tanous std::bind_front(redfish404, std::ref(app))); 27044c70412SEd Tanous 27144c70412SEd Tanous BMCWEB_ROUTE(app, "/redfish/<path>") 2720ea4b4e2SEd Tanous .methodNotAllowed() 2730ea4b4e2SEd Tanous .privileges(redfish::privileges::privilegeSetLogin)( 2740ea4b4e2SEd Tanous std::bind_front(redfish405, std::ref(app))); 2754c25d66eSEd Tanous } 276f65fca6aSEd Tanous 2774c25d66eSEd Tanous } // namespace redfish 278