diff --git a/server/control_requests.cpp b/server/control_requests.cpp index cc8e8dd3..77e7c009 100644 --- a/server/control_requests.cpp +++ b/server/control_requests.cpp @@ -1,6 +1,6 @@ /*** This file is part of snapcast - Copyright (C) 2014-2024 Johannes Pohl + Copyright (C) 2014-2025 Johannes Pohl This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,6 +34,17 @@ static constexpr auto LOG_TAG = "ControlRequest"; +/// throw InvalidParamsException if one of @p params is missing in @p request +static void checkParams(const jsonrpcpp::request_ptr& request, const std::vector& params) +{ + for (const auto& param : params) + { + if (!request->params().has(param)) + throw jsonrpcpp::InvalidParamsException("Parameter '" + param + "' is missing", request->id()); + } +} + + Request::Request(const Server& server, const std::string& method) : server_(server), method_(method) { } @@ -120,8 +131,7 @@ ClientRequest::ClientRequest(const Server& server, const std::string& method) : ClientInfoPtr ClientRequest::getClient(const jsonrpcpp::request_ptr& request) { - if (!request->params().has("id")) - throw jsonrpcpp::InvalidParamsException("Parameter 'id' is missing", request->id()); + checkParams(request, {"id"}); ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get("id")); if (clientInfo == nullptr) @@ -180,6 +190,8 @@ void ClientSetVolumeRequest::execute(const jsonrpcpp::request_ptr& request, Auth // Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}} // clang-format on + checkParams(request, {"volume"}); + std::ignore = authinfo; auto client_info = getClient(request); client_info->config.volume.fromJson(request->params().get("volume")); @@ -207,6 +219,8 @@ void ClientSetLatencyRequest::execute(const jsonrpcpp::request_ptr& request, Aut // Notification: {"jsonrpc":"2.0","method":"Client.OnLatencyChanged","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}} // clang-format on + checkParams(request, {"latency"}); + std::ignore = authinfo; int latency = request->params().get("latency"); if (latency < -10000) @@ -239,6 +253,8 @@ void ClientSetNameRequest::execute(const jsonrpcpp::request_ptr& request, AuthIn // Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}} // clang-format on + checkParams(request, {"name"}); + std::ignore = authinfo; auto client_info = getClient(request); client_info->config.name = request->params().get("name"); @@ -264,8 +280,7 @@ GroupRequest::GroupRequest(const Server& server, const std::string& method) : Re GroupPtr GroupRequest::getGroup(const jsonrpcpp::request_ptr& request) { - if (!request->params().has("id")) - throw jsonrpcpp::InvalidParamsException("Parameter 'id' is missing", request->id()); + checkParams(request, {"id"}); GroupPtr group = Config::instance().getGroup(request->params().get("id")); if (group == nullptr) @@ -306,6 +321,8 @@ void GroupSetNameRequest::execute(const jsonrpcpp::request_ptr& request, AuthInf // Notification: {"jsonrpc":"2.0","method":"Group.OnNameChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","MediaPlayer":"Laptop"}} // clang-format on + checkParams(request, {"name"}); + std::ignore = authinfo; Json result; auto group = getGroup(request); @@ -330,6 +347,8 @@ void GroupSetMuteRequest::execute(const jsonrpcpp::request_ptr& request, AuthInf // Notification: {"jsonrpc":"2.0","method":"Group.OnMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}} // clang-format on + checkParams(request, {"mute"}); + std::ignore = authinfo; bool muted = request->params().get("mute"); auto group = getGroup(request); @@ -372,6 +391,8 @@ void GroupSetStreamRequest::execute(const jsonrpcpp::request_ptr& request, AuthI // Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}} // clang-format on + checkParams(request, {"stream_id"}); + std::ignore = authinfo; auto streamId = request->params().get("stream_id"); PcmStreamPtr stream = getStreamManager().getStream(streamId); @@ -415,6 +436,8 @@ void GroupSetClientsRequest::execute(const jsonrpcpp::request_ptr& request, Auth // Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} // clang-format on + checkParams(request, {"clients"}); + std::ignore = authinfo; std::vector clients = request->params().get("clients"); @@ -496,8 +519,7 @@ PcmStreamPtr StreamRequest::getStream(const StreamManager& stream_manager, const std::string StreamRequest::getStreamId(const jsonrpcpp::request_ptr& request) { - if (!request->params().has("id")) - throw jsonrpcpp::InvalidParamsException("Parameter 'id' is missing", request->id()); + checkParams(request, {"id"}); return request->params().get("id"); } @@ -517,6 +539,8 @@ void StreamControlRequest::execute(const jsonrpcpp::request_ptr& request, AuthIn // Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}} // clang-format on + checkParams(request, {"id", "command"}); + std::ignore = authinfo; LOG(INFO, LOG_TAG) << "Stream.Control id: " << request->params().get("id") << ", command: " << request->params().get("command") << ", params: " << (request->params().has("params") ? request->params().get("params") : "") << "\n"; @@ -524,9 +548,6 @@ void StreamControlRequest::execute(const jsonrpcpp::request_ptr& request, AuthIn // Find stream PcmStreamPtr stream = getStream(getStreamManager(), request); - if (!request->params().has("command")) - throw jsonrpcpp::InvalidParamsException("Parameter 'commmand' is missing", request->id()); - auto command = request->params().get("command"); auto handle_response = [request, on_response, command](const snapcast::ErrorCode& ec) @@ -595,6 +616,8 @@ StreamSetPropertyRequest::StreamSetPropertyRequest(const Server& server) : Strea void StreamSetPropertyRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) { + checkParams(request, {"property", "value", "id"}); + LOG(INFO, LOG_TAG) << "Stream.SetProperty id: " << request->params().get("id") << ", property: " << request->params().get("property") << ", value: " << request->params().get("value") << "\n"; @@ -603,12 +626,6 @@ void StreamSetPropertyRequest::execute(const jsonrpcpp::request_ptr& request, Au std::string streamId = getStreamId(request); PcmStreamPtr stream = getStream(getStreamManager(), request); - if (!request->params().has("property")) - throw jsonrpcpp::InvalidParamsException("Parameter 'property' is missing", request->id()); - - if (!request->params().has("value")) - throw jsonrpcpp::InvalidParamsException("Parameter 'value' is missing", request->id()); - auto name = request->params().get("property"); auto value = request->params().get("value"); LOG(INFO, LOG_TAG) << "Stream '" << streamId << "' set property: " << name << " = " << value << "\n"; @@ -673,6 +690,8 @@ void StreamAddRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& // Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}} // clang-format on + checkParams(request, {"streamUri"}); + std::ignore = authinfo; LOG(INFO, LOG_TAG) << "Stream.AddStream(" << request->params().get("streamUri") << ")\n"; @@ -702,6 +721,7 @@ void StreamRemoveRequest::execute(const jsonrpcpp::request_ptr& request, AuthInf // Request: {"id":4,"jsonrpc":"2.0","method":"Stream.RemoveStream","params":{"id":"Spotify"}} // Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}} // clang-format on + checkParams(request, {"id"}); std::ignore = authinfo; LOG(INFO, LOG_TAG) << "Stream.RemoveStream(" << request->params().get("id") << ")\n"; @@ -780,6 +800,8 @@ void ServerDeleteClientRequest::execute(const jsonrpcpp::request_ptr& request, A // Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} // clang-format on + checkParams(request, {"id"}); + std::ignore = authinfo; ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get("id")); if (clientInfo == nullptr) @@ -810,10 +832,7 @@ void ServerAuthenticateRequest::execute(const jsonrpcpp::request_ptr& request, A // Response: {"id":8,"jsonrpc":"2.0","result":"ok"} // clang-format on - if (!request->params().has("scheme")) - throw jsonrpcpp::InvalidParamsException("Parameter 'scheme' is missing", request->id()); - if (!request->params().has("param")) - throw jsonrpcpp::InvalidParamsException("Parameter 'param' is missing", request->id()); + checkParams(request, {"scheme", "param"}); auto scheme = request->params().get("scheme"); auto param = request->params().get("param"); @@ -841,10 +860,8 @@ void ServerGetTokenRequest::execute(const jsonrpcpp::request_ptr& request, AuthI // Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetToken","params":{"username":"Badaix","password":"secret"}} // Response: {"id":8,"jsonrpc":"2.0","result":{"token":""}} // clang-format on - if (!request->params().has("username")) - throw jsonrpcpp::InvalidParamsException("Parameter 'username' is missing", request->id()); - if (!request->params().has("password")) - throw jsonrpcpp::InvalidParamsException("Parameter 'password' is missing", request->id()); + + checkParams(request, {"username", "password"}); auto username = request->params().get("username"); auto password = request->params().get("password"); diff --git a/server/jsonrpcpp.hpp b/server/jsonrpcpp.hpp index d76c0a67..66e41e0d 100644 --- a/server/jsonrpcpp.hpp +++ b/server/jsonrpcpp.hpp @@ -3,11 +3,11 @@ _( )/ ___) / \ ( ( \( _ \( _ \ / __)( ) ( ) / \) \\___ \( O )/ / ) / ) __/( (__(_ _)(_ _) \____/(____/ \__/ \_)__)(__\_)(__) \___)(_) (_) - version 1.4.0 + version 1.4.1 https://github.com/badaix/jsonrpcpp This file is part of jsonrpc++ - Copyright (C) 2017-2024 Johannes Pohl + Copyright (C) 2017-2025 Johannes Pohl This software may be modified and distributed under the terms of the MIT license. See the LICENSE file for details. @@ -765,7 +765,9 @@ inline bool Parameter::has(const std::string& key) const inline Json Parameter::get(const std::string& key) const { - return param_map.at(key); + if (has(key)) + return param_map.at(key); + throw RpcException("Parameter '" + key + "' not found"); } inline bool Parameter::has(size_t idx) const diff --git a/server/server.cpp b/server/server.cpp index d7e2004f..9987d7ed 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -157,7 +157,8 @@ void Server::processRequest(const jsonrpcpp::request_ptr& request, AuthInfo& aut else { LOG(ERROR, LOG_TAG) << "Method not found: " << request->method() << "\n"; - throw jsonrpcpp::MethodNotFoundException(request->id()); + auto response = std::make_shared(request->id()); + on_response(std::move(response), nullptr); } }