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"] = 108 boost::urls::format("/redfish/v1/JsonSchemas/{}", 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"] = 150 boost::urls::format("/redfish/v1/JsonSchemas/{}", 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 crow::OpenCode ec = asyncResp->res.openFile(filepath); 208 if (ec == crow::OpenCode::FileDoesNotExist) 209 { 210 messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); 211 return; 212 } 213 if (ec == crow::OpenCode::InternalError) 214 { 215 BMCWEB_LOG_DEBUG("failed to read file"); 216 messages::internalError(asyncResp->res); 217 return; 218 } 219 } 220 221 inline void requestRoutesRedfish(App& app) 222 { 223 BMCWEB_ROUTE(app, "/redfish/") 224 .methods(boost::beast::http::verb::get)( 225 std::bind_front(redfishGet, std::ref(app))); 226 227 BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>") 228 .privileges(redfish::privileges::getJsonSchemaFile) 229 .methods(boost::beast::http::verb::get)(jsonSchemaGetFile); 230 231 BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/") 232 .privileges(redfish::privileges::getJsonSchemaFileCollection) 233 .methods(boost::beast::http::verb::get)( 234 std::bind_front(jsonSchemaGet, std::ref(app))); 235 236 BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/") 237 .privileges(redfish::privileges::getJsonSchemaFile) 238 .methods(boost::beast::http::verb::get)( 239 std::bind_front(jsonSchemaIndexGet, std::ref(app))); 240 241 // Note, this route must always be registered last 242 BMCWEB_ROUTE(app, "/redfish/<path>") 243 .notFound() 244 .privileges(redfish::privileges::privilegeSetLogin)( 245 std::bind_front(redfish404, std::ref(app))); 246 247 BMCWEB_ROUTE(app, "/redfish/<path>") 248 .methodNotAllowed() 249 .privileges(redfish::privileges::privilegeSetLogin)( 250 std::bind_front(redfish405, std::ref(app))); 251 } 252 253 } // namespace redfish 254