From 78b043ad578e477099b074ea3f1c26ee234e43a8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:56:21 -0500 Subject: [PATCH] fix(confighttp): do not return 200 on errors --- docs/api.md | 2 +- src/confighttp.cpp | 112 ++++++++++++++++++++++++++++++++------------- 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/docs/api.md b/docs/api.md index 2c6e640989d..f6a95dfcb4b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -14,7 +14,7 @@ basic authentication with the admin username and password. ## POST /api/apps @copydoc confighttp::saveApp() -## DELETE /api/apps{index} +## DELETE /api/apps/{index} @copydoc confighttp::deleteApp() ## POST /api/covers/upload diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 9b8570e0817..ca1d8c10890 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -142,18 +142,65 @@ namespace confighttp { return true; } + /** + * @brief Send a 404 Not Found response. + * @param response The original response object. + * @param request The original request object. + */ + void + not_found(resp_https_t response, [[maybe_unused]] req_https_t request) { + pt::ptree tree; + tree.put("status_code", 404); + tree.put("error", "Not Found"); + + std::ostringstream data; + pt::write_json(data, tree); + + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "application/json"); + + response->write(SimpleWeb::StatusCode::client_error_not_found, data.str(), headers); + } + + /** + * @brief Send a 400 Bad Request response. + * @param response The original response object. + * @param request The original request object. + */ + void + bad_request_default(resp_https_t response, [[maybe_unused]] req_https_t request) { + pt::ptree tree; + tree.put("status_code", 400); + tree.put("error", "Invalid Request"); + + std::ostringstream data; + pt::write_json(data, tree); + + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "application/json"); + + response->write(SimpleWeb::StatusCode::client_error_bad_request, data.str(), headers); + } + + /** + * @brief Send a 400 Bad Request response. + * @param response The original response object. + * @param request The original request object. + * @param error_message The error message to include in the response. + */ void - not_found(resp_https_t response, req_https_t request) { + bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message) { pt::ptree tree; - tree.put("root..status_code", 404); + tree.put("status_code", 400); + tree.put("error", error_message); std::ostringstream data; + pt::write_json(data, tree); - pt::write_xml(data, tree); - response->write(data.str()); + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "application/json"); - *response << "HTTP/1.1 404 NOT FOUND\r\n" - << data.str(); + response->write(SimpleWeb::StatusCode::client_error_bad_request, data.str(), headers); } /** @@ -360,7 +407,7 @@ namespace confighttp { } /** - * @brief Save an application. If the application already exists, it will be updated, otherwise it will be added. + * @brief Save an application. To save a new application the index must be `-1`. To update an existing application, you must provide the current index of the application. * @param response The HTTP response object. * @param request The HTTP request object. * The body for the post request should be JSON serialized in the following format: @@ -385,7 +432,7 @@ namespace confighttp { * "detached": [ * "Detached command" * ], - * "image-path": "Full path to the application image. Must be a png file.", + * "image-path": "Full path to the application image. Must be a png file." * } * @endcode */ @@ -468,8 +515,7 @@ namespace confighttp { catch (std::exception &e) { BOOST_LOG(warning) << "SaveApp: "sv << e.what(); - outputTree.put("status", "false"); - outputTree.put("error", "Invalid Input JSON"); + bad_request(response, request, "Invalid Input JSON"); return; } @@ -502,8 +548,7 @@ namespace confighttp { int index = stoi(request->path_match[1]); if (index < 0) { - outputTree.put("status", "false"); - outputTree.put("error", "Invalid Index"); + bad_request(response, request, "Invalid Index"); return; } else { @@ -522,8 +567,7 @@ namespace confighttp { } catch (std::exception &e) { BOOST_LOG(warning) << "DeleteApp: "sv << e.what(); - outputTree.put("status", "false"); - outputTree.put("error", "Invalid File JSON"); + bad_request(response, request, "Invalid File JSON"); return; } @@ -539,7 +583,7 @@ namespace confighttp { * @code{.json} * { * "key": "igdb_", - * "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/.png", + * "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/.png" * } * @endcode */ @@ -568,8 +612,7 @@ namespace confighttp { } catch (std::exception &e) { BOOST_LOG(warning) << "UploadCover: "sv << e.what(); - outputTree.put("status", "false"); - outputTree.put("error", e.what()); + bad_request(response, request, e.what()); return; } @@ -699,8 +742,7 @@ namespace confighttp { } catch (std::exception &e) { BOOST_LOG(warning) << "SaveConfig: "sv << e.what(); - outputTree.put("status", "false"); - outputTree.put("error", e.what()); + bad_request(response, request, e.what()); return; } } @@ -757,6 +799,7 @@ namespace confighttp { print_req(request); + std::vector errors = {}; std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -779,15 +822,13 @@ namespace confighttp { auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get("confirmNewPassword") : ""; if (newUsername.length() == 0) newUsername = username; if (newUsername.length() == 0) { - outputTree.put("status", false); - outputTree.put("error", "Invalid Username"); + errors.emplace_back("Invalid Username"); } else { auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if (config::sunshine.username.empty() || (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) { if (newPassword.empty() || newPassword != confirmPassword) { - outputTree.put("status", false); - outputTree.put("error", "Password Mismatch"); + errors.emplace_back("Password Mismatch"); } else { http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword); @@ -796,15 +837,22 @@ namespace confighttp { } } else { - outputTree.put("status", false); - outputTree.put("error", "Invalid Current Credentials"); + errors.emplace_back("Invalid Current Credentials"); } } + + if (!errors.empty()) { + // join the errors array + std::string error = std::accumulate(errors.begin(), errors.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a.empty() ? b : a + ", " + b; + }); + bad_request(response, request, error); + } } catch (std::exception &e) { BOOST_LOG(warning) << "SavePassword: "sv << e.what(); - outputTree.put("status", false); - outputTree.put("error", e.what()); + bad_request(response, request, e.what()); return; } } @@ -847,8 +895,7 @@ namespace confighttp { } catch (std::exception &e) { BOOST_LOG(warning) << "SavePin: "sv << e.what(); - outputTree.put("status", false); - outputTree.put("error", e.what()); + bad_request(response, request, e.what()); return; } } @@ -912,8 +959,7 @@ namespace confighttp { } catch (std::exception &e) { BOOST_LOG(warning) << "Unpair: "sv << e.what(); - outputTree.put("status", false); - outputTree.put("error", e.what()); + bad_request(response, request, e.what()); return; } } @@ -976,7 +1022,11 @@ namespace confighttp { auto address_family = net::af_from_enum_string(config::sunshine.address_family); https_server_t server { config::nvhttp.cert, config::nvhttp.pkey }; + server.default_resource["DELETE"] = bad_request_default; server.default_resource["GET"] = not_found; + server.default_resource["PATCH"] = bad_request_default; + server.default_resource["POST"] = bad_request_default; + server.default_resource["PUT"] = bad_request_default; server.resource["^/$"]["GET"] = getIndexPage; server.resource["^/pin/?$"]["GET"] = getPinPage; server.resource["^/apps/?$"]["GET"] = getAppsPage;