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