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