@@ -191,13 +191,14 @@ namespace confighttp {
191191 * @brief Send a 404 Not Found response.
192192 * @param response The HTTP response object.
193193 * @param request The HTTP request object.
194+ * @param error_message The error message to include in the response.
194195 */
195- void not_found (resp_https_t response, [[maybe_unused]] req_https_t request) {
196+ void not_found (resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = " Not Found " ) {
196197 constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found;
197198
198199 nlohmann::json tree;
199200 tree[" status_code" ] = code;
200- tree[" error" ] = " Not Found " ;
201+ tree[" error" ] = error_message ;
201202
202203 SimpleWeb::CaseInsensitiveMultimap headers;
203204 headers.emplace (" Content-Type" , " application/json" );
@@ -262,6 +263,28 @@ namespace confighttp {
262263 return true ;
263264 }
264265
266+ /* *
267+ * @brief Validates the application index and sends error response if invalid.
268+ * @param response The HTTP response object.
269+ * @param request The HTTP request object.
270+ * @param index The application index/id.
271+ */
272+ bool check_app_index (resp_https_t response, req_https_t request, int index) {
273+ std::string file = file_handler::read_file (config::stream.file_apps .c_str ());
274+ nlohmann::json file_tree = nlohmann::json::parse (file);
275+ if (const auto &apps = file_tree[" apps" ]; index < 0 || index >= static_cast <int >(apps.size ())) {
276+ std::string error;
277+ if (const int max_index = static_cast <int >(apps.size ()) - 1 ; max_index < 0 ) {
278+ error = " No applications found" ;
279+ } else {
280+ error = std::format (" 'index' {} out of range, max index is {}" , index, max_index);
281+ }
282+ bad_request (std::move (response), std::move (request), error);
283+ return false ;
284+ }
285+ return true ;
286+ }
287+
265288 /* *
266289 * @brief Get the index page.
267290 * @param response The HTTP response object.
@@ -711,25 +734,19 @@ namespace confighttp {
711734 try {
712735 nlohmann::json output_tree;
713736 nlohmann::json new_apps = nlohmann::json::array ();
714- std::string file = file_handler::read_file (config::stream.file_apps .c_str ());
715- nlohmann::json file_tree = nlohmann::json::parse (file);
716- auto &apps_node = file_tree[" apps" ];
717737 const int index = std::stoi (request->path_match [1 ]);
718738
719- if (index < 0 || index >= static_cast <int >(apps_node.size ())) {
720- std::string error;
721- if (const int max_index = static_cast <int >(apps_node.size ()) - 1 ; max_index < 0 ) {
722- error = " No applications to delete" ;
723- } else {
724- error = std::format (" 'index' {} out of range, max index is {}" , index, max_index);
725- }
726- bad_request (response, request, error);
739+ if (!check_app_index (response, request, index)) {
727740 return ;
728741 }
729742
730- for (size_t i = 0 ; i < apps_node.size (); ++i) {
743+ std::string file = file_handler::read_file (config::stream.file_apps .c_str ());
744+ nlohmann::json file_tree = nlohmann::json::parse (file);
745+ auto &apps = file_tree[" apps" ];
746+
747+ for (size_t i = 0 ; i < apps.size (); ++i) {
731748 if (i != index) {
732- new_apps.push_back (apps_node [i]);
749+ new_apps.push_back (apps [i]);
733750 }
734751 }
735752 file_tree[" apps" ] = new_apps;
@@ -928,6 +945,67 @@ namespace confighttp {
928945 }
929946 }
930947
948+ /* *
949+ * @brief Get an application's image.
950+ * @param response The HTTP response object.
951+ * @param request The HTTP request object.
952+ *
953+ * @note{The index in the url path is the application index.}
954+ *
955+ * @api_examples{/api/covers/9999 | GET| null}
956+ */
957+ void getCover (resp_https_t response, req_https_t request) {
958+ if (!check_content_type (response, request, " application/json" )) {
959+ return ;
960+ }
961+ if (!authenticate (response, request)) {
962+ return ;
963+ }
964+
965+ print_req (request);
966+
967+ try {
968+ const int index = std::stoi (request->path_match [1 ]);
969+ if (!check_app_index (response, request, index)) {
970+ return ;
971+ }
972+
973+ std::string file = file_handler::read_file (config::stream.file_apps .c_str ());
974+ nlohmann::json file_tree = nlohmann::json::parse (file);
975+ auto &apps = file_tree[" apps" ];
976+
977+ auto &app = apps[index];
978+
979+ // Get the image path from the app configuration
980+ std::string app_image_path;
981+ if (app.contains (" image-path" ) && !app[" image-path" ].is_null ()) {
982+ app_image_path = app[" image-path" ];
983+ }
984+
985+ // Use validate_app_image_path to resolve and validate the path
986+ // This handles extension validation, PNG signature validation, and path resolution
987+ std::string validated_path = proc::validate_app_image_path (app_image_path);
988+
989+ // Open and stream the validated file
990+ std::ifstream in (validated_path, std::ios::binary);
991+ if (!in) {
992+ BOOST_LOG (warning) << " Unable to read cover image file: " << validated_path;
993+ bad_request (response, request, " Unable to read cover image file" );
994+ return ;
995+ }
996+
997+ SimpleWeb::CaseInsensitiveMultimap headers;
998+ headers.emplace (" Content-Type" , " image/png" );
999+ headers.emplace (" X-Frame-Options" , " DENY" );
1000+ headers.emplace (" Content-Security-Policy" , " frame-ancestors 'none';" );
1001+
1002+ response->write (SimpleWeb::StatusCode::success_ok, in, headers);
1003+ } catch (std::exception &e) {
1004+ BOOST_LOG (warning) << " GetCover: " sv << e.what ();
1005+ bad_request (response, request, e.what ());
1006+ }
1007+ }
1008+
9311009 /* *
9321010 * @brief Upload a cover image.
9331011 * @param response The HTTP response object.
@@ -1324,7 +1402,9 @@ namespace confighttp {
13241402 server.default_resource [" PUT" ] = [](resp_https_t response, req_https_t request) {
13251403 bad_request (response, request);
13261404 };
1327- server.default_resource [" GET" ] = not_found;
1405+ server.default_resource [" GET" ] = [](resp_https_t response, req_https_t request) {
1406+ not_found (response, request);
1407+ };
13281408 server.resource [" ^/$" ][" GET" ] = getIndexPage;
13291409 server.resource [" ^/pin/?$" ][" GET" ] = getPinPage;
13301410 server.resource [" ^/apps/?$" ][" GET" ] = getAppsPage;
@@ -1351,6 +1431,7 @@ namespace confighttp {
13511431 server.resource [" ^/api/clients/unpair$" ][" POST" ] = unpair;
13521432 server.resource [" ^/api/apps/close$" ][" POST" ] = closeApp;
13531433 server.resource [" ^/api/covers/upload$" ][" POST" ] = uploadCover;
1434+ server.resource [" ^/api/covers/([0-9]+)$" ][" GET" ] = getCover;
13541435 server.resource [" ^/images/sunshine.ico$" ][" GET" ] = getFaviconImage;
13551436 server.resource [" ^/images/logo-sunshine-45.png$" ][" GET" ] = getSunshineLogoImage;
13561437 server.resource [" ^/assets\\ /.+$" ][" GET" ] = getNodeModules;
0 commit comments