<ClCompile Include="..\..\xbmc\interfaces\json-rpc\FileItemHandler.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\FileOperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONRPC.cpp" />
+ <ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\PicturePlayerOperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\PlayerOperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\PlaylistOperations.cpp" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\IClient.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\ITransportLayer.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONRPC.h" />
+ <ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONUtils.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\PicturePlayerOperations.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\PlayerOperations.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\PlaylistOperations.h" />
+ <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ServiceDescription.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\SystemOperations.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\VideoLibrary.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\XBMCOperations.h" />
<UserProperties RESOURCE_FILE="XBMC_PC.rc" />
</VisualStudio>
</ProjectExtensions>
-</Project>
\ No newline at end of file
+</Project>
<ClCompile Include="..\..\xbmc\dialogs\GUIDialogPlayEject.cpp">
<Filter>dialogs</Filter>
</ClCompile>
+ <ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.cpp">
+ <Filter>interfaces\json-rpc</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\xbmc\win32\pch.h">
<ClInclude Include="..\..\xbmc\dialogs\GUIDialogPlayEject.h">
<Filter>dialogs</Filter>
</ClInclude>
+ <ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.h">
+ <Filter>interfaces\json-rpc</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ServiceDescription.h">
+ <Filter>interfaces\json-rpc</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\xbmc\win32\XBMC.ico">
#ifdef HAS_JSONRPC
if (g_guiSettings.GetBool("services.esenabled"))
{
+ CJSONRPC::Initialize();
+
if (CTCPServer::StartServer(g_advancedSettings.m_jsonTcpPort, g_guiSettings.GetBool("services.esallinterfaces")))
CZeroconf::GetInstance()->PublishService("servers.jsonrpc", "_xbmc-jsonrpc._tcp", "XBMC JSONRPC", g_advancedSettings.m_jsonTcpPort);
}
if (!FillFileItemList(parameterObject, list))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
-
- if (param["index"].isInt())
- indexValue = param["index"].asInt();
+ if (parameterObject["index"].isInt())
+ indexValue = parameterObject["index"].asInt();
g_application.getApplicationMessenger().PlayListPlayerInsert(GetPlaylist(method), list, indexValue);
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!ParameterIntOrNull(param, "genreid"))
- return InvalidParams;
+ //if (!ParameterIntOrNull(parameterObject, "genreid"))
+ // return InvalidParams;
CMusicDatabase musicdatabase;
if (!musicdatabase.Open())
return InternalError;
- int genreID = ParameterAsInt(param, -1, "genreid");
+ int genreID = parameterObject.get("genreid", -1).asInt();
CFileItemList items;
if (musicdatabase.GetArtistsNav("", items, genreID, false))
- HandleFileItemList("artistid", false, "artists", items, param, result);
+ HandleFileItemList("artistid", false, "artists", items, parameterObject, result);
musicdatabase.Close();
return OK;
{
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
-
- const Value param = ForceObject(parameterObject);
- if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "genreid")))
- return InvalidParams;
+
+ //if (!(ParameterIntOrNull(parameterObject, "artistid") || ParameterIntOrNull(parameterObject, "albumid")))
+ // return InvalidParams;
CMusicDatabase musicdatabase;
if (!musicdatabase.Open())
return InternalError;
- int artistID = ParameterAsInt(param, -1, "artistid");
- int genreID = ParameterAsInt(param, -1, "genreid");
- int start = ParameterAsInt(param, -1, "start");
- int end = ParameterAsInt(param, -1, "end");
+ int artistID = parameterObject.get("artistid", -1).asInt();
+ int genreID = parameterObject.get("genreid", -1).asInt();
+ int start = parameterObject.get("start", -1).asInt();
+ int end = parameterObject.get("end", -1).asInt();
CFileItemList items;
if (musicdatabase.GetAlbumsNav("", items, genreID, artistID, start, end))
- HandleFileItemList("albumid", false, "albums", items, param, result);
+ HandleFileItemList("albumid", false, "albums", items, parameterObject, result);
musicdatabase.Close();
return OK;
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!(ParameterIntOrNull(param, "albumid")))
- return InvalidParams;
+ //if (!(ParameterIntOrNull(parameterObject, "albumid")))
+ // return InvalidParams;
- int albumID = ParameterAsInt(param, -1, "albumid");
+ int albumID = parameterObject.get("albumid", -1).asInt();
if (albumID <= 0)
return InvalidParams;
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "albumid") || ParameterIntOrNull(param, "genreid")))
- return InvalidParams;
+ //if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "albumid") || ParameterIntOrNull(param, "genreid")))
+ // return InvalidParams;
CMusicDatabase musicdatabase;
if (!musicdatabase.Open())
return InternalError;
- int artistID = ParameterAsInt(param, -1, "artistid");
- int albumID = ParameterAsInt(param, -1, "albumid");
- int genreID = ParameterAsInt(param, -1, "genreid");
+ int artistID = parameterObject.get("artistid", -1).asInt();
+ int albumID = parameterObject.get("albumid", -1).asInt();
+ int genreID = parameterObject.get("genreid", -1).asInt();
CFileItemList items;
if (musicdatabase.GetSongsNav("", items, genreID, artistID, albumID))
- HandleFileItemList("songid", true, "songs", items, param, result);
+ HandleFileItemList("songid", true, "songs", items, parameterObject, result);
musicdatabase.Close();
return OK;
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!(ParameterIntOrNull(param, "songid")))
- return InvalidParams;
+ //if (!(ParameterIntOrNull(parameterObject, "songid")))
+ // return InvalidParams;
- int idSong = ParameterAsInt(param, -1, "songid");
+ int idSong = parameterObject.get("songid", -1).asInt();
if (idSong <= 0)
return InvalidParams;
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
-
CMusicDatabase musicdatabase;
if (!musicdatabase.Open())
return InternalError;
CFileItemList items;
if (musicdatabase.GetGenresNav("", items))
- HandleFileItemList("genreid", true, "genres", items, param, result);
+ HandleFileItemList("genreid", true, "genres", items, parameterObject, result);
musicdatabase.Close();
return OK;
{
if (parameterObject["artistid"].isInt() || parameterObject["albumid"].isInt() || parameterObject["genreid"].isInt())
{
- int artistID = ParameterAsInt(parameterObject, -1, "artistid");
- int albumID = ParameterAsInt(parameterObject, -1, "albumid");
- int genreID = ParameterAsInt(parameterObject, -1, "genreid");
+ int artistID = parameterObject.get("artistid", -1).asInt();
+ int albumID = parameterObject.get("albumid", -1).asInt();
+ int genreID = parameterObject.get("genreid", -1).asInt();
success = musicdatabase.GetSongsNav("", list, genreID, artistID, albumID);
}
if (parameterObject["songid"].isInt())
{
- int songID = ParameterAsInt(parameterObject, -1, "songid");
+ int songID = parameterObject.get("songid", -1).asInt();
if (songID != -1)
{
CSong song;
bool CFileItemHandler::FillFileItemList(const Value ¶meterObject, CFileItemList &list)
{
- Value param = ForceObject(parameterObject);
+ //if (parameterObject.isString())
+ // parameterObject["file"] = parameterObject.asString();
- if (parameterObject.isString())
- param["file"] = parameterObject.asString();
-
- if (param["file"].isString())
+ if (parameterObject["file"].isString())
{
- CStdString file = param["file"].asString();
+ CStdString file = parameterObject["file"].asString();
CFileItemPtr item = CFileItemPtr(new CFileItem(file, URIUtils::HasSlashAtEnd(file)));
list.Add(item);
}
- CPlaylistOperations::FillFileItemList(param, list);
- CAudioLibrary::FillFileItemList(param, list);
- CVideoLibrary::FillFileItemList(param, list);
- CFileOperations::FillFileItemList(param, list);
+ CPlaylistOperations::FillFileItemList(parameterObject, list);
+ CAudioLibrary::FillFileItemList(parameterObject, list);
+ CVideoLibrary::FillFileItemList(parameterObject, list);
+ CFileOperations::FillFileItemList(parameterObject, list);
return true;
}
#include "utils/log.h"
#include "utils/Variant.h"
#include <string.h>
+#include "ServiceDescription.h"
using namespace ANNOUNCEMENT;
using namespace JSONRPC;
using namespace Json;
using namespace std;
-Command CJSONRPC::m_commands[] = {
+bool CJSONRPC::m_initialized = false;
+
+JsonRpcMethodMap CJSONRPC::m_methodMaps[] = {
+// JSON-RPC
+ { "JSONRPC.Introspect", CJSONRPC::Introspect },
+ { "JSONRPC.Version", CJSONRPC::Version },
+ { "JSONRPC.Permission", CJSONRPC::Permission },
+ { "JSONRPC.Ping", CJSONRPC::Ping },
+ { "JSONRPC.GetAnnouncementFlags", CJSONRPC::GetAnnouncementFlags },
+ { "JSONRPC.SetAnnouncementFlags", CJSONRPC::SetAnnouncementFlags },
+ { "JSONRPC.Announce", CJSONRPC::Announce },
+
+// Player
+// { "Player.GetActivePlayers", CPlayerOperations::GetActivePlayers },
+
+// Music player
+// { "AudioPlayer.State", CAVPlayerOperations::State },
+// { "AudioPlayer.PlayPause", CAVPlayerOperations::PlayPause },
+// { "AudioPlayer.Stop", CAVPlayerOperations::Stop },
+// { "AudioPlayer.SkipPrevious", CAVPlayerOperations::SkipPrevious },
+// { "AudioPlayer.SkipNext", CAVPlayerOperations::SkipNext },
+//
+// { "AudioPlayer.BigSkipBackward", CAVPlayerOperations::BigSkipBackward },
+// { "AudioPlayer.BigSkipForward", CAVPlayerOperations::BigSkipForward },
+// { "AudioPlayer.SmallSkipBackward", CAVPlayerOperations::SmallSkipBackward },
+// { "AudioPlayer.SmallSkipForward", CAVPlayerOperations::SmallSkipForward },
+//
+// { "AudioPlayer.Rewind", CAVPlayerOperations::Rewind },
+// { "AudioPlayer.Forward", CAVPlayerOperations::Forward },
+//
+// { "AudioPlayer.GetTime", CAVPlayerOperations::GetTime },
+// { "AudioPlayer.GetTimeMS", CAVPlayerOperations::GetTimeMS },
+// { "AudioPlayer.GetPercentage", CAVPlayerOperations::GetPercentage },
+// { "AudioPlayer.SeekTime", CAVPlayerOperations::SeekTime },
+// { "AudioPlayer.SeekPercentage", CAVPlayerOperations::SeekPercentage },
+//
+// { "AudioPlayer.Record", CAVPlayerOperations::Record },
+
+// Video player
+// { "VideoPlayer.State", CAVPlayerOperations::State },
+// { "VideoPlayer.PlayPause", CAVPlayerOperations::PlayPause },
+// { "VideoPlayer.Stop", CAVPlayerOperations::Stop },
+// { "VideoPlayer.SkipPrevious", CAVPlayerOperations::SkipPrevious },
+// { "VideoPlayer.SkipNext", CAVPlayerOperations::SkipNext },
+//
+// { "VideoPlayer.BigSkipBackward", CAVPlayerOperations::BigSkipBackward },
+// { "VideoPlayer.BigSkipForward", CAVPlayerOperations::BigSkipForward },
+// { "VideoPlayer.SmallSkipBackward", CAVPlayerOperations::SmallSkipBackward },
+// { "VideoPlayer.SmallSkipForward", CAVPlayerOperations::SmallSkipForward },
+//
+// { "VideoPlayer.Rewind", CAVPlayerOperations::Rewind },
+// { "VideoPlayer.Forward", CAVPlayerOperations::Forward },
+//
+// { "VideoPlayer.GetTime", CAVPlayerOperations::GetTime },
+// { "VideoPlayer.GetTimeMS", CAVPlayerOperations::GetTimeMS },
+// { "VideoPlayer.GetPercentage", CAVPlayerOperations::GetPercentage },
+// { "VideoPlayer.SeekTime", CAVPlayerOperations::SeekTime },
+// { "VideoPlayer.SeekPercentage", CAVPlayerOperations::SeekPercentage },
+
+// Picture player
+// { "PicturePlayer.PlayPause", CPicturePlayerOperations::PlayPause },
+// { "PicturePlayer.Stop", CPicturePlayerOperations::Stop },
+// { "PicturePlayer.SkipPrevious", CPicturePlayerOperations::SkipPrevious },
+// { "PicturePlayer.SkipNext", CPicturePlayerOperations::SkipNext },
+//
+// { "PicturePlayer.MoveLeft", CPicturePlayerOperations::MoveLeft },
+// { "PicturePlayer.MoveRight", CPicturePlayerOperations::MoveRight },
+// { "PicturePlayer.MoveDown", CPicturePlayerOperations::MoveDown },
+// { "PicturePlayer.MoveUp", CPicturePlayerOperations::MoveUp },
+//
+// { "PicturePlayer.ZoomOut", CPicturePlayerOperations::ZoomOut },
+// { "PicturePlayer.ZoomIn", CPicturePlayerOperations::ZoomIn },
+// { "PicturePlayer.Zoom", CPicturePlayerOperations::Zoom },
+// { "PicturePlayer.Rotate", CPicturePlayerOperations::Rotate },
+
+// Video Playlist
+ // TODO
+
+// Audio Playlist
+ // TODO
+
+// Playlist
+// { "Playlist.Create", CPlaylistOperations::Create },
+// { "Playlist.Destroy", CPlaylistOperations::Destroy },
+//
+// { "Playlist.GetItems", CPlaylistOperations::GetItems },
+// { "Playlist.Add", CPlaylistOperations::Add },
+// { "Playlist.Remove", CPlaylistOperations::Remove },
+// { "Playlist.Swap", CPlaylistOperations::Swap },
+// { "Playlist.Shuffle", CPlaylistOperations::Shuffle },
+// { "Playlist.UnShuffle", CPlaylistOperations::UnShuffle },
+
+// Files
+// { "Files.GetSources", CFileOperations::GetRootDirectory },
+// { "Files.Download", CFileOperations::Download },
+// { "Files.GetDirectory", CFileOperations::GetDirectory },
+
+// Music Library
+ // TODO
+
+// Video Library
+ // TODO
+
+// System operations
+// { "System.Shutdown", CSystemOperations::Shutdown },
+// { "System.Suspend", CSystemOperations::Suspend },
+// { "System.Hibernate", CSystemOperations::Hibernate },
+// { "System.Reboot", CSystemOperations::Reboot },
+// { "System.GetInfoLabels", CSystemOperations::GetInfoLabels },
+// { "System.GetInfoBooleans", CSystemOperations::GetInfoBooleans },
+
+// XBMC operations
+// { "XBMC.GetVolume", CXBMCOperations::GetVolume },
+// { "XBMC.SetVolume", CXBMCOperations::SetVolume },
+// { "XBMC.ToggleMute", CXBMCOperations::ToggleMute },
+// { "XBMC.Play", CXBMCOperations::Play },
+// { "XBMC.StartSlideshow", CXBMCOperations::StartSlideshow },
+// { "XBMC.Log", CXBMCOperations::Log },
+// { "XBMC.Quit", CXBMCOperations::Quit }
+};
+
+/*Command CJSONRPC::m_commands[] = {
// JSON-RPC
{ "JSONRPC.Introspect", CJSONRPC::Introspect, Response, ReadData, "Enumerates all actions and descriptions. Parameter example {\"getdescriptions\": true, \"getpermissions\": true, \"filterbytransport\": true }. All parameters optional" },
{ "JSONRPC.Version", CJSONRPC::Version, Response, ReadData, "Retrieve the jsonrpc protocol version" },
{ "XBMC.Log", CXBMCOperations::Log, Response, Logging, "Logs a line in the xbmc.log. Parameter example {\"message\": \"foo\", \"level\": \"info\"} or just a string to log message with level debug" },
{ "XBMC.Quit", CXBMCOperations::Quit, Response, ControlPower, "Quit xbmc" }
-};
+};*/
-CJSONRPC::CActionMap CJSONRPC::m_actionMap(m_commands, sizeof(m_commands) / sizeof(m_commands[0]) );
-
-JSON_STATUS CJSONRPC::Introspect(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
+void CJSONRPC::Initialize()
{
- if (!(parameterObject.isObject() || parameterObject.isNull()))
- return InvalidParams;
-
- const Value param = parameterObject.isObject() ? parameterObject : Value(objectValue);
- bool getDescriptions = param.get("getdescriptions", true).asBool();
- bool getPermissions = param.get("getpermissions", true).asBool();
- bool filterByTransport = param.get("filterbytransport", true).asBool();
-
- int length = sizeof(m_commands) / sizeof(Command);
- int clientflags = client->GetPermissionFlags();
- for (int i = 0; i < length; i++)
- {
- if ((transport->GetCapabilities() & m_commands[i].transportneed) == 0 && filterByTransport)
- continue;
+ if (m_initialized)
+ return;
- Value val;
+ unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap);
+ if (!CJSONServiceDescription::Parse(m_methodMaps, size))
+ CLog::Log(LOGSEVERE, "JSONRPC: Error while parsing the json rpc service description");
+ else
+ CLog::Log(LOGINFO, "JSONRPC: json rpc service description successfully loaded");
+
+ m_initialized = true;
+}
- val["command"] = m_commands[i].command;
- val["executable"] = (clientflags & m_commands[i].permission) > 0 ? true : false;
- if (getDescriptions && m_commands[i].description)
- val["description"] = m_commands[i].description;
- if (getPermissions)
- val["permission"] = PermissionToString(m_commands[i].permission);
+JSON_STATUS CJSONRPC::Introspect(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
+{
+ bool getDescriptions = parameterObject["getdescriptions"].asBool();
+ bool getMetadata = parameterObject["getmetadata"].asBool();
+ bool filterByTransport = parameterObject["filterbytransport"].asBool();
- result["commands"].append(val);
- }
+ CJSONServiceDescription::Print(result, transport, client, getDescriptions, getMetadata, filterByTransport);
return OK;
}
JSON_STATUS CJSONRPC::Version(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
{
- result["version"] = 3;
+ result["version"] = CJSONServiceDescription::GetVersion();
return OK;
}
JSON_STATUS CJSONRPC::SetAnnouncementFlags(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
{
- if (!parameterObject.isObject())
- return InvalidParams;
-
int flags = 0;
- if (parameterObject.get("Playback", false).asBool())
+ if (parameterObject["Playback"].asBool())
flags |= Playback;
- if (parameterObject.get("GUI", false).asBool())
+ if (parameterObject["GUI"].asBool())
flags |= GUI;
- if (parameterObject.get("System", false).asBool())
+ if (parameterObject["System"].asBool())
flags |= System;
- if (parameterObject.get("Library", false).asBool())
+ if (parameterObject["Library"].asBool())
flags |= Library;
- if (parameterObject.get("Other", false).asBool())
+ if (parameterObject["Other"].asBool())
flags |= Other;
if (client->SetAnnouncementFlags(flags))
JSON_STATUS CJSONRPC::Announce(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
{
- if (!parameterObject.isObject() || !parameterObject.isMember("sender") || !parameterObject.isMember("message"))
- return InvalidParams;
-
- if (!parameterObject.isMember("data"))
- CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(), parameterObject["message"].asString().c_str());
+ if (parameterObject["data"].isNull())
+ CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(),
+ parameterObject["message"].asString().c_str());
else
{
CVariant data(parameterObject["data"].asString());
- CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(), parameterObject["message"].asString().c_str(), data);
+ CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(),
+ parameterObject["message"].asString().c_str(), data);
}
return ACK;
{
isAnnouncement = !request.isMember("id");
- CStdString method = request.get("method", "").asString();
- method = method.ToLower();
- errorCode = InternalMethodCall(method, request, result, transport, client);
+ CStdString methodName = request.get("method", "").asString();
+ methodName = methodName.ToLower();
+
+ JSONRPC::MethodCall method;
+ Json::Value params;
+
+ if ((errorCode = CJSONServiceDescription::CheckCall(methodName, request["params"], client, isAnnouncement, method, params)) == OK)
+ errorCode = method(methodName, transport, client, params, result);
+ else
+ result = params;
}
else
{
return !isAnnouncement;
}
-JSON_STATUS CJSONRPC::InternalMethodCall(const CStdString& method, Value& o, Value &result, ITransportLayer *transport, IClient *client)
-{
- CActionMap::const_iterator iter = m_actionMap.find(method);
- if( iter != m_actionMap.end() )
- {
- if (client->GetPermissionFlags() & iter->second.permission)
- return iter->second.method(method, transport, client, o["params"], result);
- else
- return BadPermission;
- }
- else
- return MethodNotFound;
-}
-
inline bool CJSONRPC::IsProperJSONRPC(const Json::Value& inputroot)
{
- return inputroot.isObject() && inputroot.isMember("jsonrpc") && inputroot["jsonrpc"].isString() && inputroot.get("jsonrpc", "-1").asString() == "2.0" && inputroot.isMember("method") && inputroot["method"].isString();
+ return inputroot.isObject() && inputroot.isMember("jsonrpc") && inputroot["jsonrpc"].isString() && inputroot.get("jsonrpc", "-1").asString() == "2.0" && inputroot.isMember("method") && inputroot["method"].isString() && (!inputroot.isMember("params") || inputroot["params"].isArray() || inputroot["params"].isObject());
}
inline void CJSONRPC::BuildResponse(const Value& request, JSON_STATUS code, const Value& result, Value& response)
case InvalidParams:
response["error"]["code"] = InvalidParams;
response["error"]["message"] = "Invalid params.";
+ if (!result.isNull())
+ response["error"]["data"] = result;
break;
case MethodNotFound:
response["error"]["code"] = MethodNotFound;
break;
}
}
-
-inline const char *CJSONRPC::PermissionToString(const OperationPermission &permission)
-{
- switch (permission)
- {
- case ReadData:
- return "ReadData";
- case ControlPlayback:
- return "ControlPlayback";
- case ControlAnnounce:
- return "ControlAnnounce";
- case ControlPower:
- return "ControlPower";
- case Logging:
- return "Logging";
- case ScanLibrary:
- return "ScanLibrary";
- default:
- return "Unknown";
- }
-}
-
-inline const char *CJSONRPC::AnnouncementFlagToString(const EAnnouncementFlag &announcement)
-{
- switch (announcement)
- {
- case Playback:
- return "Playback";
- case GUI:
- return "GUI";
- case System:
- return "System";
- case Library:
- return "Library";
- case Other:
- return "Other";
- default:
- return "Unknown";
- }
-}
-
-CJSONRPC::CActionMap::CActionMap(const Command commands[], int length)
-{
- for (int i = 0; i < length; i++)
- {
- CStdString command = commands[i].command;
- command = command.ToLower();
- m_actionmap[command] = commands[i];
- }
-}
-
-CJSONRPC::CActionMap::const_iterator CJSONRPC::CActionMap::find(const CStdString& key) const
-{
- return m_actionmap.find(key);
-}
-
-CJSONRPC::CActionMap::const_iterator CJSONRPC::CActionMap::end() const
-{
- return m_actionmap.end();
-}
#include "ITransportLayer.h"
#include "interfaces/IAnnouncer.h"
#include "jsoncpp/include/json/json.h"
+#include "JSONUtils.h"
+#include "JSONServiceDescription.h"
namespace JSONRPC
{
- enum JSON_STATUS
- {
- OK = 0,
- ACK = -1,
- InvalidRequest = -32600,
- MethodNotFound = -32601,
- InvalidParams = -32602,
- InternalError = -32603,
- ParseError = -32700,
- //-32099..-32000 Reserved for implementation-defined server-errors.
- BadPermission = -32099,
- FailedToExecute = -32100
- };
-
- /* The method call needs to be perfectly threadsafe
- The method will only be called if the caller has the correct permissions. The method will need to check parameters for bad parametervalues.
- */
- typedef JSON_STATUS (*MethodCall) (const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
+ /*!
+ \ingroup jsonrpc
+ \brief JSON RPC handler
- enum OperationPermission
+ Sets up and manages all needed information to process
+ JSON RPC requests and answering with the appropriate
+ JSON RPC response (actual response or error message).
+ */
+ class CJSONRPC : public CJSONUtils
{
- ReadData = 0x1,
- ControlPlayback = 0x2,
- ControlAnnounce = 0x4,
- ControlPower = 0x8,
- Logging = 0x10,
- ScanLibrary = 0x20,
- };
+ public:
+ /*!
+ \brief Initializes the JSON RPC handler
+ */
+ static void Initialize();
- static const int OPERATION_PERMISSION_ALL = (ReadData | ControlPlayback | ControlAnnounce | ControlPower | Logging | ScanLibrary);
+ /*
+ \brief Handles an incoming JSON RPC request
+ \param inputString received JSON RPC request
+ \param transport Transport protocol on which the request arrived
+ \param client Client which sent the request
+ \return JSON RPC response to be sent back to the client
- typedef struct
- {
- const char* command;
- MethodCall method;
- TransportLayerCapability transportneed;
- OperationPermission permission;
- const char* description;
- } Command;
-
- class CJSONRPC
- {
- public:
+ Parses the received input string for the called method and provided
+ parameters. If the request does not conform to the JSON RPC 2.0
+ specification an error is returned. Otherwise the parameters provided
+ in the request are checked for validity and completeness. If the request
+ is valid and the requested method exists it is called and executed.
+ */
static CStdString MethodCall(const CStdString &inputString, ITransportLayer *transport, IClient *client);
static JSON_STATUS Introspect(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
static JSON_STATUS GetAnnouncementFlags(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
static JSON_STATUS SetAnnouncementFlags(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
static JSON_STATUS Announce(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
+
private:
+ static void setup();
static bool HandleMethodCall(Json::Value& request, Json::Value& response, ITransportLayer *transport, IClient *client);
- static JSON_STATUS InternalMethodCall(const CStdString& method, Json::Value& o, Json::Value &result, ITransportLayer *transport, IClient *client);
static inline bool IsProperJSONRPC(const Json::Value& inputroot);
inline static void BuildResponse(const Json::Value& request, JSON_STATUS code, const Json::Value& result, Json::Value& response);
- inline static const char *PermissionToString(const OperationPermission &permission);
- inline static const char *AnnouncementFlagToString(const ANNOUNCEMENT::EAnnouncementFlag &announcement);
-
- class CActionMap
- {
- public:
- CActionMap(const Command commands[], int length);
-
- typedef std::map<CStdString, Command>::const_iterator const_iterator;
- const_iterator find(const CStdString& key) const;
- const_iterator end() const;
- private:
- std::map<CStdString, Command> m_actionmap;
- };
- static Command m_commands[];
- static CActionMap m_actionMap;
+ static JsonRpcMethodMap m_methodMaps[];
+ static bool m_initialized;
};
}
--- /dev/null
+/*
+ * Copyright (C) 2005-2010 Team XBMC
+ * http://www.xbmc.org
+ *
+ * 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
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <limits>
+#include "ServiceDescription.h"
+#include "JSONServiceDescription.h"
+#include "utils/log.h"
+#include "utils/StringUtils.h"
+
+using namespace std;
+using namespace JSONRPC;
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap()
+{
+ m_propertiesmap = std::map<CStdString, JSONSchemaTypeDefinition>();
+}
+
+void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add(JSONSchemaTypeDefinition &property)
+{
+ CStdString name = property.name;
+ name = name.ToLower();
+ m_propertiesmap[name] = property;
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const
+{
+ return m_propertiesmap.begin();
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const CStdString& key) const
+{
+ return m_propertiesmap.find(key);
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::end() const
+{
+ return m_propertiesmap.end();
+}
+
+unsigned int JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::size() const
+{
+ return m_propertiesmap.size();
+}
+
+Json::Value CJSONServiceDescription::m_notifications = Json::Value(Json::objectValue);
+CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap;
+JsonRpcDescriptionHeader CJSONServiceDescription::m_header;
+std::map<CStdString, JSONSchemaTypeDefinition> CJSONServiceDescription::m_types = std::map<CStdString, JSONSchemaTypeDefinition>();
+std::vector<CStdString> CJSONServiceDescription::m_badMethods = std::vector<CStdString>();
+bool CJSONServiceDescription::m_newReferenceType = false;
+
+bool CJSONServiceDescription::Parse(const JsonRpcMethodMap methodMap[], unsigned int size)
+{
+ Json::Value descriptionObject;
+ Json::Reader reader;
+
+ // Read the json schema for notifications
+ if (!reader.parse(JSON_NOTIFICATION_DESCRIPTION, m_notifications))
+ CLog::Log(LOGERROR, "JSONRPC: Unable to read the json schema notification description");
+
+ // Read the json schema service descriptor and check if it represents
+ // a json object and contains a "services" element for methods
+ if (!reader.parse(JSON_SERVICE_DESCRIPTION, descriptionObject))
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Unable to read the json schema service description");
+ return false;
+ }
+
+ // First parse the header
+ parseHeader(descriptionObject);
+
+ // Get all child elements
+ Json::Value::Members methodNames = descriptionObject.getMemberNames();
+
+ // Loop through the methods
+ for (unsigned int index = 0; index < methodNames.size(); index++)
+ {
+ CStdString methodName = methodNames.at(index);
+ // Make sure the method actually exists and represents an object
+ if (!descriptionObject.isMember(methodName) || !descriptionObject[methodName].isObject() ||
+ !descriptionObject[methodName].isMember("type") || !descriptionObject[methodName]["type"].isString())
+ continue;
+
+ CStdString type = GetString(descriptionObject[methodName]["type"], "");
+ if (type.compare("method") == 0)
+ {
+ // Check if the method is available in the method map
+ JSONRPC::MethodCall methodCall = NULL;
+ unsigned int mapIndex;
+ for (mapIndex = 0; mapIndex < size; mapIndex++)
+ {
+ if (methodName.compare(methodMap[mapIndex].name) == 0)
+ {
+ methodCall = methodMap[mapIndex].method;
+ break;
+ }
+ }
+
+ // If the method is not available in the method map
+ // we have to ignore it
+ if (methodCall == NULL)
+ {
+ CLog::Log(LOGERROR, "JSONRPC: No implementation for method %s found", methodName.c_str());
+ continue;
+ }
+
+ // Parse the details of the method
+ JsonRpcMethod method;
+ method.name = methodMap[mapIndex].name;
+ method.method = methodCall;
+ if (!parseMethod(descriptionObject[methodName], method))
+ {
+ // If parsing failed add the method to the list of currently bad methods
+ // (might be that a reference for a parameter is missing)
+ m_badMethods.push_back(methodName);
+ CLog::Log(LOGDEBUG, "JSONRPC: Method %s could not be parsed correctly and might be re-parsed later", methodName.c_str());
+ continue;
+ }
+
+ m_actionMap.add(method);
+ }
+ else if (descriptionObject[methodName].isMember("id") && descriptionObject[methodName]["id"].isString())
+ {
+ JSONSchemaTypeDefinition globalType;
+ globalType.name = methodName.c_str();
+ parseTypeDefinition(descriptionObject[methodName], globalType, false);
+ }
+ }
+
+ // As long as there have been new reference types
+ // and there are more bad methods than in the last
+ // try we can try parsing again
+ unsigned int badMethodCount = m_badMethods.size() + 1;
+ while (m_newReferenceType && m_badMethods.size() > 0 && m_badMethods.size() < badMethodCount)
+ {
+ m_newReferenceType = false;
+ badMethodCount = m_badMethods.size();
+ std::vector<CStdString> stillBadMethods = std::vector<CStdString>();
+
+ for (unsigned int methodIndex = 0; methodIndex < badMethodCount; methodIndex++)
+ {
+ JsonRpcMethod method;
+ // Check if the method is available in the method map
+ JSONRPC::MethodCall methodCall = NULL;
+ unsigned int mapIndex;
+ for (mapIndex = 0; mapIndex < size; mapIndex++)
+ {
+ if (m_badMethods.at(methodIndex).compare(methodMap[mapIndex].name) == 0)
+ {
+ methodCall = methodMap[mapIndex].method;
+ break;
+ }
+ }
+
+ // If the method is not available in the method map
+ // we have to ignore it
+ if (methodCall == NULL)
+ {
+ CLog::Log(LOGERROR, "JSONRPC: No implementation for method %s found", m_badMethods.at(methodIndex).c_str());
+ continue;
+ }
+
+ // Parse the details of the method
+ method.name = methodMap[mapIndex].name;
+ method.method = methodCall;
+ if (!parseMethod(descriptionObject[m_badMethods.at(methodIndex)], method))
+ {
+ // If parsing still failed add the method to the list of currently bad methods
+ // (might be that a reference for a parameter is missing)
+ stillBadMethods.push_back(m_badMethods.at(methodIndex));
+ CLog::Log(LOGDEBUG, "JSONRPC: Method %s could not be parsed correctly and might be re-parsed later", m_badMethods.at(methodIndex).c_str());
+ continue;
+ }
+
+ m_actionMap.add(method);
+ }
+
+ m_badMethods = stillBadMethods;
+ }
+
+ // Print a log message for every unparseable method
+ for (unsigned int badMethodIndex = 0; badMethodIndex < m_badMethods.size(); badMethodIndex++)
+ CLog::Log(LOGERROR, "JSONRPC: Method %s could not be parsed correctly and will be ignored", m_badMethods.at(badMethodIndex).c_str());
+
+ return true;
+}
+
+int CJSONServiceDescription::GetVersion()
+{
+ return m_header.version;
+}
+
+void CJSONServiceDescription::Print(Json::Value &result, ITransportLayer *transport, IClient *client, bool printDescriptions, bool printMetadata, bool filterByTransport)
+{
+ // Print the header
+ result["id"] = m_header.id;
+ result["version"] = m_header.version;
+ result["description"] = m_header.description;
+
+ std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator typeIterator;
+ std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator typeIteratorEnd = m_types.end();
+ for (typeIterator = m_types.begin(); typeIterator != typeIteratorEnd; typeIterator++)
+ {
+ Json::Value currentType = Json::Value(Json::objectValue);
+ printType(typeIterator->second, false, true, true, printDescriptions, currentType);
+
+ result["types"][typeIterator->first] = currentType;
+ }
+
+ // Iterate through all json rpc methods
+ int clientPermissions = client->GetPermissionFlags();
+ int transportCapabilities = transport->GetCapabilities();
+
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end();
+ for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++)
+ {
+ if ((clientPermissions & methodIterator->second.permission) == 0 || ((transportCapabilities & methodIterator->second.transportneed) == 0 && filterByTransport))
+ continue;
+
+ Json::Value currentMethod = Json::Value(Json::objectValue);
+
+ currentMethod["type"] = "method";
+ if (printDescriptions && strlen(methodIterator->second.description) > 0)
+ currentMethod["description"] = methodIterator->second.description;
+ if (printMetadata)
+ {
+ currentMethod["permission"] = PermissionToString(methodIterator->second.permission);
+ currentMethod["statechanging"] = methodIterator->second.stateChanging;
+ }
+
+ currentMethod["params"] = Json::Value(Json::arrayValue);
+ for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++)
+ {
+ Json::Value param = Json::Value(Json::objectValue);
+ printType(methodIterator->second.parameters.at(paramIndex), true, false, true, printDescriptions, param);
+ currentMethod["params"].append(param);
+ }
+
+ currentMethod["returns"] = methodIterator->second.returns;
+
+ result["methods"][methodIterator->second.name] = currentMethod;
+ }
+
+ // Print notification description
+ Json::Value::Members notifications = m_notifications.getMemberNames();
+ for (unsigned int notifIndex = 0; notifIndex < notifications.size(); notifIndex++)
+ {
+ if (!m_notifications.isMember(notifications.at(notifIndex)) ||
+ !m_notifications[notifications.at(notifIndex)].isObject() ||
+ !m_notifications[notifications.at(notifIndex)].isMember("type") ||
+ !m_notifications[notifications.at(notifIndex)]["type"].isString() ||
+ m_notifications[notifications.at(notifIndex)]["type"].asString().compare("notification") != 0)
+ {
+ continue;
+ }
+
+ result["notifications"][notifications.at(notifIndex)] = m_notifications[notifications.at(notifIndex)];
+ }
+}
+
+JSON_STATUS CJSONServiceDescription::CheckCall(const char* const method, const Json::Value &requestParameters, IClient *client, bool announcement, MethodCall &methodCall, Json::Value &outputParameters)
+{
+ CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method);
+ if (iter != m_actionMap.end())
+ {
+ if (client != NULL && (client->GetPermissionFlags() & iter->second.permission) && (!announcement || iter->second.stateChanging))
+ {
+ methodCall = iter->second.method;
+
+ // Count the number of actually handled (present)
+ // parameters
+ unsigned int handled = 0;
+ Json::Value errorData = Json::Value(Json::objectValue);
+ errorData["method"] = iter->second.name;
+
+ // Loop through all the parameters to check
+ for (unsigned int i = 0; i < iter->second.parameters.size(); i++)
+ {
+ // Evaluate the current parameter
+ JSON_STATUS status = checkParameter(requestParameters, iter->second.parameters.at(i), i, outputParameters, handled, errorData);
+ if (status != OK)
+ {
+ // Return the error data object in the outputParameters reference
+ outputParameters = errorData;
+ return status;
+ }
+ }
+
+ // Check if there were unnecessary parameters
+ if (handled < requestParameters.size())
+ {
+ errorData["message"] = "Too many parameters";
+ outputParameters = errorData;
+ return InvalidParams;
+ }
+
+ return OK;
+ }
+ else
+ return BadPermission;
+ }
+ else
+ return MethodNotFound;
+}
+
+void CJSONServiceDescription::printType(const JSONSchemaTypeDefinition &type, bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, Json::Value &output)
+{
+ bool typeReference = false;
+
+ // Printing general fields
+ if (isParameter)
+ output["name"] = type.name;
+
+ if (isGlobal)
+ output["id"] = type.id;
+ else if (type.id != NULL && strlen(type.id) > 0)
+ {
+ output["$ref"] = type.id;
+ typeReference = true;
+ }
+
+ if (printDescriptions && strlen(type.description) > 0)
+ output["description"] = type.description;
+
+ if (isParameter || printDefault)
+ {
+ if (!type.optional)
+ output["required"] = true;
+ if (type.optional && type.type != ObjectValue && type.type != ArrayValue)
+ output["default"] = type.defaultValue;
+ }
+
+ if (!typeReference)
+ {
+ SchemaValueTypeToJson(type.type, output["type"]);
+
+ // Printing enum field
+ if (type.enums.size() > 0)
+ {
+ output["enums"] = Json::Value(Json::arrayValue);
+ for (unsigned int enumIndex = 0; enumIndex < type.enums.size(); enumIndex++)
+ output["enums"].append(type.enums.at(enumIndex));
+ }
+
+ // Printing integer/number fields
+ if (HasType(type.type, IntegerValue) || HasType(type.type, NumberValue))
+ {
+ if (HasType(type.type, NumberValue))
+ {
+ if (type.minimum > -numeric_limits<double>::max())
+ output["minimum"] = type.minimum;
+ if (type.maximum < numeric_limits<double>::max())
+ output["maximum"] = type.maximum;
+ }
+ else
+ {
+ if (type.minimum > numeric_limits<int>::min())
+ output["minimum"] = (int)type.minimum;
+ if (type.maximum < numeric_limits<int>::max())
+ output["maximum"] = (int)type.maximum;
+ }
+
+ if (type.exclusiveMinimum)
+ output["exclusiveMinimum"] = true;
+ if (type.exclusiveMaximum)
+ output["exclusiveMaximum"] = true;
+ if (type.divisibleBy > 0)
+ output["divisibleBy"] = type.divisibleBy;
+ }
+
+ // Print array fields
+ if (HasType(type.type, ArrayValue))
+ {
+ if (type.items.size() == 1)
+ {
+ printType(type.items.at(0), false, false, false, printDescriptions, output["items"]);
+ }
+ else if (type.items.size() > 1)
+ {
+ output["items"] = Json::Value(Json::arrayValue);
+ for (unsigned int itemIndex = 0; itemIndex < type.items.size(); itemIndex++)
+ {
+ Json::Value item = Json::Value(Json::objectValue);
+ printType(type.items.at(itemIndex), false, false, false, printDescriptions, item);
+ output["items"].append(item);
+ }
+ }
+
+ if (type.minItems > 0)
+ output["minItems"] = type.minItems;
+ if (type.maxItems > 0)
+ output["maxItems"] = type.maxItems;
+
+ if (type.additionalItems.size() == 1)
+ {
+ printType(type.additionalItems.at(0), false, false, false, printDescriptions, output["additionalItems"]);
+ }
+ else if (type.additionalItems.size() > 1)
+ {
+ output["additionalItems"] = Json::Value(Json::arrayValue);
+ for (unsigned int addItemIndex = 0; addItemIndex < type.additionalItems.size(); addItemIndex++)
+ {
+ Json::Value item = Json::Value(Json::objectValue);
+ printType(type.additionalItems.at(addItemIndex), false, false, false, printDescriptions, item);
+ output["additionalItems"].append(item);
+ }
+ }
+
+ if (type.uniqueItems)
+ output["uniqueItems"] = true;
+ }
+
+ // Print object fields
+ if (HasType(type.type, ObjectValue) && type.properties.size() > 0)
+ {
+ output["properties"] = Json::Value(Json::objectValue);
+
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = type.properties.end();
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
+ for (propertiesIterator = type.properties.begin(); propertiesIterator != propertiesEnd; propertiesIterator++)
+ {
+ printType(propertiesIterator->second, false, false, true, printDescriptions, output["properties"][propertiesIterator->first]);
+ }
+ }
+ }
+}
+
+JSON_STATUS CJSONServiceDescription::checkParameter(const Json::Value &requestParameters, const JSONSchemaTypeDefinition &type, unsigned int position, Json::Value &outputParameters, unsigned int &handled, Json::Value &errorData)
+{
+ // Let's check if the parameter has been provided
+ if (ParameterExists(requestParameters, type.name, position))
+ {
+ // Get the parameter
+ Json::Value parameterValue = GetParameter(requestParameters, type.name, position);
+
+ // Evaluate the type of the parameter
+ JSON_STATUS status = checkType(parameterValue, type, outputParameters[type.name], errorData["stack"]);
+ if (status != OK)
+ return status;
+
+ // The parameter was present and valid
+ handled++;
+ }
+ // If the parameter has not been provided but is optional
+ // we can use its default value
+ else if (type.optional)
+ outputParameters[type.name] = type.defaultValue;
+ // The parameter is required but has not been provided => invalid
+ else
+ {
+ errorData["stack"]["name"] = type.name;
+ SchemaValueTypeToJson(type.type, errorData["stack"]["type"]);
+ errorData["stack"]["message"] = "Missing parameter";
+ return InvalidParams;
+ }
+
+ return OK;
+}
+
+JSON_STATUS CJSONServiceDescription::checkType(const Json::Value &value, const JSONSchemaTypeDefinition &type, Json::Value &outputValue, Json::Value &errorData)
+{
+ if (type.name != NULL)
+ errorData["name"] = type.name;
+ SchemaValueTypeToJson(type.type, errorData["type"]);
+ CStdString errorMessage;
+
+ // Let's check the type of the provided parameter
+ if (!IsType(value, type.type))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Type mismatch in type %s", type.name);
+ errorMessage.Format("Invalid type %s received", ValueTypeToString(value.type()));
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ else if (value.isNull() && !HasType(type.type, NullValue))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Value is NULL in type %s", type.name);
+ errorData["message"] = "Received value is null";
+ return InvalidParams;
+ }
+
+ // If it is an array we need to
+ // - check the type of every element ("items")
+ // - check if they need to be unique ("uniqueItems")
+ if (HasType(type.type, ArrayValue) && value.isArray())
+ {
+ // Check the number of items against minItems and maxItems
+ if ((type.minItems > 0 && value.size() < type.minItems) || (type.maxItems > 0 && value.size() > type.maxItems))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Number of array elements does not match minItems and/or maxItems in type %s", type.name);
+ if (type.minItems > 0 && type.maxItems > 0)
+ errorMessage.Format("Between %d and %d array items expected but %d received", type.minItems, type.maxItems, value.size());
+ else if (type.minItems > 0)
+ errorMessage.Format("At least %d array items expected but only %d received", type.minItems, value.size());
+ else
+ errorMessage.Format("Only %d array items expected but %d received", type.maxItems, value.size());
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+
+ if (type.items.size() == 0)
+ {
+ outputValue = value;
+ }
+ else if (type.items.size() == 1)
+ {
+ JSONSchemaTypeDefinition itemType = type.items.at(0);
+
+ // Loop through all array elements
+ for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++)
+ {
+ JSON_STATUS status = checkType(value[arrayIndex], itemType, outputValue[arrayIndex], errorData["property"]);
+ if (status != OK)
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Array element at index %u does not match in type %s", arrayIndex, type.name);
+ errorMessage.Format("%s expected for array element at index %u but %s received", SchemaValueTypeToString(type.type), arrayIndex, ValueTypeToString(value.type()));
+ errorData["message"] = errorMessage.c_str();
+ return status;
+ }
+ }
+ }
+ // We have more than one element in "items"
+ // so we have tuple typing, which means that
+ // every element in the value array must match
+ // with the type at the same position in the
+ // "items" array
+ else
+ {
+ // If the number of elements in the value array
+ // does not match the number of elements in the
+ // "items" array and additional items are not
+ // allowed there is no need to check every element
+ if (value.size() < type.items.size() || (value.size() != type.items.size() && type.additionalItems.size() == 0))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: One of the array elements does not match in type %s", type.name);
+ errorMessage.Format("%d array elements expected but %d received", type.items.size(), value.size());
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+
+ // Loop through all array elements until there
+ // are either no more schemas in the "items"
+ // array or no more elements in the value's array
+ unsigned int arrayIndex;
+ for (arrayIndex = 0; arrayIndex < min(type.items.size(), value.size()); arrayIndex++)
+ {
+ JSON_STATUS status = checkType(value[arrayIndex], type.items.at(arrayIndex), outputValue[arrayIndex], errorData["property"]);
+ if (status != OK)
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Array element at index %u does not match with items schema in type %s", arrayIndex, type.name);
+ return status;
+ }
+ }
+
+ if (type.additionalItems.size() > 0)
+ {
+ // Loop through the rest of the elements
+ // in the array and check them against the
+ // "additionalItems"
+ for (; arrayIndex < value.size(); arrayIndex++)
+ {
+ bool ok = false;
+ for (unsigned int additionalIndex = 0; additionalIndex < type.additionalItems.size(); additionalIndex++)
+ {
+ Json::Value dummyError;
+ if (checkType(value[arrayIndex], type.additionalItems.at(additionalIndex), outputValue[arrayIndex], dummyError) == OK)
+ {
+ ok = true;
+ break;
+ }
+ }
+
+ if (!ok)
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Array contains non-conforming additional items in type %s", type.name);
+ errorMessage.Format("Array element at index %u does not match the \"additionalItems\" schema", arrayIndex);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+ }
+ }
+
+ // If every array element is unique we need to check each one
+ if (type.uniqueItems)
+ {
+ for (unsigned int checkingIndex = 0; checkingIndex < outputValue.size(); checkingIndex++)
+ {
+ for (unsigned int checkedIndex = checkingIndex + 1; checkedIndex < outputValue.size(); checkedIndex++)
+ {
+ // If two elements are the same they are not unique
+ if (Compare(outputValue[checkingIndex], outputValue[checkedIndex]) == 0)
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Not unique array element at index %u and %u in type %s", checkingIndex, checkedIndex, type.name);
+ errorMessage.Format("Array element at index %u is not unique (same as array element at index %u)", checkingIndex, checkedIndex);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+ }
+ }
+
+ return OK;
+ }
+
+ // If it is an object we need to check every element
+ // against the defined "properties"
+ if (HasType(type.type, ObjectValue) && value.isObject())
+ {
+ unsigned int handled = 0;
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = type.properties.end();
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
+ for (propertiesIterator = type.properties.begin(); propertiesIterator != propertiesEnd; propertiesIterator++)
+ {
+ if (value.isMember(propertiesIterator->first))
+ {
+ JSON_STATUS status = checkType(value[propertiesIterator->first], propertiesIterator->second, outputValue[propertiesIterator->first], errorData["property"]);
+ if (status != OK)
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Invalid property \"%s\" in type %s", propertiesIterator->first.c_str(), type.name);
+ return status;
+ }
+ handled++;
+ }
+ else if (propertiesIterator->second.optional)
+ outputValue[propertiesIterator->first] = propertiesIterator->second.defaultValue;
+ else
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Missing property \"%s\" in type %s", propertiesIterator->first.c_str(), type.name);
+ errorData["property"]["name"] = propertiesIterator->first.c_str();
+ errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second.type);
+ errorData["message"] = "Missing property";
+ return InvalidParams;
+ }
+ }
+
+ // Additional properties are not allowed
+ if (handled < value.getMemberNames().size())
+ {
+ errorData["message"] = "Unexpected additional properties received";
+ return InvalidParams;
+ }
+
+ return OK;
+ }
+
+ // It's neither an array nor an object
+
+ // If it can only take certain values ("enum")
+ // we need to check against those
+ if (type.enums.size() > 0)
+ {
+ bool valid = false;
+ for (unsigned int enumIndex = 0; enumIndex < type.enums.size(); enumIndex++)
+ {
+ if (Compare(type.enums.at(enumIndex), value) == 0)
+ {
+ valid = true;
+ break;
+ }
+ }
+
+ if (!valid)
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Value does not match any of the enum values in type %s", type.name);
+ errorData["message"] = "Received value does not match any of the defined enum values";
+ return InvalidParams;
+ }
+ }
+
+ // If we have a number or an integer type, we need
+ // to check the minimum and maximum values
+ if (HasType(type.type, NumberValue) || HasType(type.type, IntegerValue) && value.isNumeric())
+ {
+ double numberValue = value.asDouble();
+ // Check minimum
+ if ((type.exclusiveMinimum && numberValue <= type.minimum) || (!type.exclusiveMinimum && numberValue < type.minimum) ||
+ // Check maximum
+ (type.exclusiveMaximum && numberValue >= type.maximum) || (!type.exclusiveMaximum && numberValue > type.maximum))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Value does not lay between minimum and maximum in type %s", type.name);
+ errorMessage.Format("Value between %f (%s) and %f (%s) expected but %f received",
+ type.minimum, type.exclusiveMinimum ? "exclusive" : "inclusive", type.maximum, type.exclusiveMaximum ? "exclusive" : "inclusive", numberValue);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ // Check divisibleBy
+ else if ((HasType(type.type, IntegerValue) && type.divisibleBy > 0 && ((int)numberValue % type.divisibleBy) != 0))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Value does not meet divisibleBy requirements in type %s", type.name);
+ errorMessage.Format("Value should be divisible by %d but %d received", type.divisibleBy, (int)numberValue);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+
+ // Otherwise it can have any value
+ outputValue = value;
+ return OK;
+}
+
+void CJSONServiceDescription::parseHeader(const Json::Value &descriptionObject)
+{
+ m_header.id = GetString(descriptionObject["id"], "");
+ m_header.version = descriptionObject.get("version", 0).asInt();
+ m_header.description = GetString(descriptionObject["description"], "");
+}
+
+bool CJSONServiceDescription::parseMethod(const Json::Value &value, JsonRpcMethod &method)
+{
+ // Parse XBMC specific information about the method
+ method.transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString().c_str() : "");
+ method.permission = StringToPermission(value.isMember("permission") ? value["permission"].asString().c_str() : "");
+ method.description = GetString(value["description"], "");
+ method.stateChanging = value.isMember("statechanging") ? value["statechanging"].asBool() : false;
+
+ // Check whether there are parameters defined
+ if (value.isMember("params") && value["params"].isArray())
+ {
+ // Loop through all defined parameters
+ for (unsigned int paramIndex = 0; paramIndex < value["params"].size(); paramIndex++)
+ {
+ Json::Value parameter = value["params"][paramIndex];
+ // If the parameter definition does not contain a valid "name" or
+ // "type" element we will ignore it
+ if (!parameter.isMember("name") || !parameter["name"].isString() ||
+ (!parameter.isMember("type") && !parameter.isMember("$ref")) ||
+ (parameter.isMember("type") && !parameter["type"].isString() &&
+ !parameter["type"].isArray()) || (parameter.isMember("$ref") &&
+ !parameter["$ref"].isString()))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Method %s has a badly defined parameter", method.name);
+ return false;
+ }
+
+ // Parse the parameter and add it to the list
+ // of defined parameters
+ JSONSchemaTypeDefinition param;
+ if (!parseParameter(parameter, param))
+ return false;
+ method.parameters.push_back(param);
+ }
+ }
+
+ // Parse the return value of the method
+ parseReturn(value, method.returns);
+
+ return true;
+}
+
+bool CJSONServiceDescription::parseParameter(Json::Value &value, JSONSchemaTypeDefinition ¶meter)
+{
+ parameter.name = GetString(value["name"], "");
+
+ // Parse the type and default value of the parameter
+ return parseTypeDefinition(value, parameter, true);
+}
+
+bool CJSONServiceDescription::parseTypeDefinition(const Json::Value &value, JSONSchemaTypeDefinition &type, bool isParameter)
+{
+ bool isReferenceType = false;
+ bool hasReference = false;
+
+ type.id = NULL;
+ type.description = NULL;
+
+ // Check if the type of the parameter defines a json reference
+ // to a type defined somewhere else
+ if (value.isMember("$ref") && value["$ref"].isString())
+ {
+ // Get the name of the referenced type
+ CStdString refType = value["$ref"].asString();
+ // Check if the referenced type exists
+ std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator iter = m_types.find(refType);
+ if (refType.length() <= 0 || iter == m_types.end())
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type %s references an unknown type %s", type.name, refType.c_str());
+ return false;
+ }
+
+ const char *typeName = type.name;
+ type = iter->second;
+ if (strlen(typeName) > 0)
+ type.name = typeName;
+ hasReference = true;
+ }
+ else if (value.isMember("id") && value["id"].isString())
+ {
+ type.id = GetString(value["id"], "");
+ isReferenceType = true;
+ }
+
+ // Check if the "required" field has been defined
+ type.optional = value.isMember("required") && value["required"].isBool() ? !value["required"].asBool() : true;
+
+ // Get the "description"
+ if (!hasReference || (value.isMember("description") && value["description"].isString()))
+ type.description = GetString(value["description"], "");
+
+ if (hasReference)
+ {
+ // If there is a specific default value, read it
+ if (value.isMember("default") && IsType(value["default"], type.type))
+ {
+ bool ok = false;
+ if (type.enums.size() >= 0)
+ ok = true;
+ // If the type has an enum definition we must make
+ // sure that the default value is a valid enum value
+ else
+ {
+ for (unsigned int defIndex = 0; defIndex < type.enums.size(); defIndex++)
+ {
+ if (Compare(value["default"], type.enums.at(defIndex)) == 0)
+ {
+ ok = true;
+ break;
+ }
+ }
+ }
+
+ if (ok)
+ type.defaultValue = value["default"];
+ }
+
+ return true;
+ }
+
+ // Get the defined type of the parameter
+ if (value["type"].isArray())
+ {
+ int parsedType = 0;
+ // If the defined type is an array, we have
+ // to handle a union type
+ for (unsigned int typeIndex = 0; typeIndex < value["type"].size(); typeIndex++)
+ {
+ // If the type is a string try to parse it
+ if (value["type"][typeIndex].isString())
+ parsedType |= StringToSchemaValueType(value["type"][typeIndex].asString().c_str());
+ else
+ CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition of type %s", type.name);
+ }
+
+ type.type = (JSONSchemaType)parsedType;
+
+ // If the type has not been set yet
+ // set it to "any"
+ if (type.type == 0)
+ type.type = AnyValue;
+ }
+ else
+ type.type = value["type"].isString() ? StringToSchemaValueType(value["type"].asString().c_str()) : AnyValue;
+
+ if (type.type == ObjectValue)
+ {
+ // If the type definition is of type "object"
+ // and has a "properties" definition we need
+ // to handle these as well
+ if (value.isMember("properties") && value["properties"].isObject())
+ {
+ // Get all child elements of the "properties"
+ // object and loop through them
+ Json::Value::Members properties = value["properties"].getMemberNames();
+ for (unsigned int propertyIndex = 0; propertyIndex < properties.size(); propertyIndex++)
+ {
+ CStdString propertyName = properties.at(propertyIndex);
+ if (!value["properties"].isMember(propertyName))
+ continue;
+
+ // Create a new type definition, store the name
+ // of the current property into it, parse it
+ // recursively and add its default value
+ // to the current type's default value
+ JSONSchemaTypeDefinition propertyType;
+ propertyType.name = propertyName.c_str();
+ if (!parseTypeDefinition(value["properties"][propertyName], propertyType, false))
+ return false;
+ type.defaultValue[propertyName] = propertyType.defaultValue;
+ type.properties.add(propertyType);
+ }
+ }
+ }
+ else
+ {
+ // If the defined parameter is an array
+ // we need to check for detailed definitions
+ // of the array items
+ if (type.type == ArrayValue)
+ {
+ // Check for "uniqueItems" field
+ if (value.isMember("uniqueItems") && value["uniqueItems"].isBool())
+ type.uniqueItems = value["uniqueItems"].asBool();
+ else
+ type.uniqueItems = false;
+
+ // Check for "additionalItems" field
+ if (value.isMember("additionalItems"))
+ {
+ // If it is an object, there is only one schema for it
+ if (value["additionalItems"].isObject())
+ {
+ JSONSchemaTypeDefinition additionalItem;
+ additionalItem.name = NULL;
+
+ if (parseTypeDefinition(value["additionalItems"], additionalItem, false))
+ type.additionalItems.push_back(additionalItem);
+ }
+ // If it is an array there may be multiple schema definitions
+ else if (value["additionalItems"].isArray())
+ {
+ for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++)
+ {
+ JSONSchemaTypeDefinition additionalItem;
+ additionalItem.name = NULL;
+
+ if (parseTypeDefinition(value["additionalItems"][itemIndex], additionalItem, false))
+ type.additionalItems.push_back(additionalItem);
+ }
+ }
+ // If it is not a (array of) schema and not a bool (default value is false)
+ // it has an invalid value
+ else if (!value["additionalItems"].isBool())
+ CLog::Log(LOGWARNING, "Invalid \"additionalItems\" value for type %s", type.name);
+ }
+
+ // If the "items" field is a single object
+ // we can parse that directly
+ if (value.isMember("items"))
+ {
+ if (value["items"].isObject())
+ {
+ JSONSchemaTypeDefinition item;
+ item.name = NULL;
+
+ if (!parseTypeDefinition(value["items"], item, false))
+ return false;
+ type.items.push_back(item);
+ }
+ // Otherwise if it is an array we need to
+ // parse all elements and store them
+ else if (value["items"].isArray())
+ {
+ for (unsigned int itemIndex = 0; itemIndex < value["items"].size(); itemIndex++)
+ {
+ JSONSchemaTypeDefinition item;
+ item.name = NULL;
+
+ if (!parseTypeDefinition(value["items"][itemIndex], item, false))
+ return false;
+ type.items.push_back(item);
+ }
+ }
+ }
+
+ type.minItems = value.get("minItems", 0).asInt();
+ type.maxItems = value.get("maxItems", 0).asInt();
+ }
+ // The type is whether an object nor an array
+ else
+ {
+ if ((type.type & NumberValue) == NumberValue || (type.type & IntegerValue) == IntegerValue)
+ {
+ if ((type.type & NumberValue) == NumberValue)
+ {
+ type.minimum = value.get("minimum", -numeric_limits<double>::max()).asDouble();
+ type.maximum = value.get("maximum", numeric_limits<double>::max()).asDouble();
+ }
+ else if ((type.type & IntegerValue) == IntegerValue)
+ {
+ type.minimum = value.get("minimum", numeric_limits<int>::min()).asInt();
+ type.maximum = value.get("maximum", numeric_limits<int>::max()).asInt();
+ }
+
+ type.exclusiveMinimum = value.get("exclusiveMinimum", false).asBool();
+ type.exclusiveMaximum = value.get("exclusiveMaximum", false).asBool();
+ type.divisibleBy = value.get("divisibleBy", 0).asUInt();
+ }
+
+ // If the type definition is neither an
+ // "object" nor an "array" we can check
+ // for an "enum" definition
+ if (value.isMember("enum") && value["enum"].isArray())
+ {
+ // Loop through all elements in the "enum" array
+ for (unsigned int enumIndex = 0; enumIndex < value["enum"].size(); enumIndex++)
+ {
+ // Check for duplicates and eliminate them
+ bool approved = true;
+ for (unsigned int approvedIndex = 0; approvedIndex < type.enums.size(); approvedIndex++)
+ {
+ if (Compare(value["enum"][enumIndex], type.enums.at(approvedIndex)) == 0)
+ {
+ approved = false;
+ break;
+ }
+ }
+
+ // Only add the current item to the enum value
+ // list if it is not duplicate
+ if (approved)
+ type.enums.push_back(value["enum"][enumIndex]);
+ }
+ }
+ }
+
+ // If there is a definition for a default value and its type
+ // matches the type of the parameter we can parse it
+ bool ok = false;
+ if (value.isMember("default") && IsType(value["default"], type.type))
+ {
+ if (type.enums.size() >= 0)
+ ok = true;
+ // If the type has an enum definition we must make
+ // sure that the default value is a valid enum value
+ else
+ {
+ for (unsigned int defIndex = 0; defIndex < type.enums.size(); defIndex++)
+ {
+ if (Compare(value["default"], type.enums.at(defIndex)) == 0)
+ {
+ ok = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (ok)
+ type.defaultValue = value["default"];
+ else
+ {
+ // If the type of the default value definition does not
+ // match the type of the parameter we have to log this
+ if (value.isMember("default") && !IsType(value["default"], type.type))
+ CLog::Log(LOGWARNING, "JSONRPC: Parameter %s has an invalid default value", type.name);
+
+ // If the type contains an "enum" we need to get the
+ // default value from the first enum value
+ if (type.enums.size() > 0)
+ type.defaultValue = type.enums.at(0);
+ // otherwise set a default value instead
+ else
+ SetDefaultValue(type.defaultValue, type.type);
+ }
+ }
+
+ if (isReferenceType)
+ addReferenceTypeDefinition(type);
+
+ return true;
+}
+
+void CJSONServiceDescription::parseReturn(const Json::Value &value, Json::Value &returns)
+{
+ // Only parse the "returns" definition if there is one
+ if (value.isMember("returns"))
+ returns = value["returns"];
+}
+
+void CJSONServiceDescription::addReferenceTypeDefinition(JSONSchemaTypeDefinition &typeDefinition)
+{
+ // If the given json value is no object or does not contain an "id" field
+ // of type string it is no valid type definition
+ if (typeDefinition.id == NULL || strlen(typeDefinition.id) <= 0)
+ return;
+
+ // If the id has already been defined we ignore the type definition
+ if (m_types.find(typeDefinition.id) != m_types.end())
+ return;
+
+ // Add the type to the list of type definitions
+ m_types[typeDefinition.id] = typeDefinition;
+ if (m_badMethods.size() > 0)
+ m_newReferenceType = true;
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap()
+{
+ m_actionmap = std::map<CStdString, JsonRpcMethod>();
+}
+
+void CJSONServiceDescription::CJsonRpcMethodMap::add(JsonRpcMethod &method)
+{
+ CStdString name = method.name;
+ name = name.ToLower();
+ m_actionmap[name] = method;
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const
+{
+ return m_actionmap.begin();
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const CStdString& key) const
+{
+ return m_actionmap.find(key);
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const
+{
+ return m_actionmap.end();
+}
--- /dev/null
+#pragma once
+/*
+ * Copyright (C) 2005-2010 Team XBMC
+ * http://www.xbmc.org
+ *
+ * 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
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include "utils/StdString.h"
+#include <string>
+#include <vector>
+#include "jsoncpp/include/json/json.h"
+#include "JSONUtils.h"
+
+namespace JSONRPC
+{
+ /*!
+ \ingroup jsonrpc
+ \brief Structure for the header of
+ of a service mapping description.
+
+ Represents the header of a service mapping
+ description containing its version, id,
+ description, transport, envelope, contentType,
+ target and additionalParameters fields.
+ */
+ typedef struct
+ {
+ /*!
+ \brief Identification of the published
+ service containing json rpc methods.
+ */
+ const char* id;
+ /*!
+ \brief Description of the published
+ service containing json rpc methods.
+ */
+ const char* description;
+
+ /*!
+ \brief Version of the published
+ service containing json rpc methods.
+ */
+ int version;
+ } JsonRpcDescriptionHeader;
+
+ /*!
+ \ingroup jsonrpc
+ \brief Structure for a parameter of a
+ json rpc method.
+
+ Represents a parameter of a defined
+ json rpc method and is used to verify
+ and extract the value of the parameter
+ in a method call.
+ */
+ typedef struct JSONSchemaTypeDefinition
+ {
+ /*!
+ \brief Name of the parameter (for
+ by-name calls)
+ */
+ const char* name;
+
+ /*!
+ \brief Id of the type (for
+ referenced types)
+ */
+ const char* id;
+
+ /*!
+ \brief Description of the parameter
+ */
+ const char* description;
+
+ /*!
+ \brief JSON schema type of the parameter's value
+ */
+ JSONSchemaType type;
+
+ /*!
+ \brief Whether or not the parameter is
+ optional
+ */
+ bool optional;
+
+ /*!
+ \brief Default value of the parameter
+ (only needed when it is optional)
+ */
+ Json::Value defaultValue;
+
+ /*!
+ \brief Minimum value for Integer
+ or Number types
+ */
+ double minimum;
+
+ /*!
+ \brief Maximum value for Integer or Number types
+ */
+ double maximum;
+
+ /*!
+ \brief Whether to exclude the defined Minimum
+ value from the valid range or not
+ */
+ bool exclusiveMinimum;
+
+ /*!
+ \brief Whether to exclude the defined Maximum
+ value from the valid range or not
+ */
+ bool exclusiveMaximum;
+
+ /*!
+ \brief Integer by which the value (of type
+ Integer) must be divisible without rest
+ */
+ unsigned int divisibleBy;
+
+ /*!
+ \brief (Optional) List of allowed values
+ for the type
+ */
+ std::vector<Json::Value> enums;
+
+ /*!
+ \brief List of possible values in an array
+ */
+ std::vector<JSONSchemaTypeDefinition> items;
+
+ /*!
+ \brief Minimum amount of items in the array
+ */
+ unsigned minItems;
+
+ /*!
+ \brief Maximum amount of items in the array
+ */
+ unsigned maxItems;
+
+ /*!
+ \brief Whether every value in the array
+ must be unique or not
+ */
+ bool uniqueItems;
+
+ /*!
+ \brief List of json schema definitions for
+ additional items in an array with tuple
+ typing (defined schemas in "items")
+ */
+ std::vector<JSONSchemaTypeDefinition> additionalItems;
+
+ /*!
+ \brief Maps a properties name to its
+ json schema type definition
+ */
+ class CJsonSchemaPropertiesMap
+ {
+ public:
+ CJsonSchemaPropertiesMap();
+
+ void add(JSONSchemaTypeDefinition &property);
+
+ typedef std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator JSONSchemaPropertiesIterator;
+ JSONSchemaPropertiesIterator begin() const;
+ JSONSchemaPropertiesIterator find(const CStdString& key) const;
+ JSONSchemaPropertiesIterator end() const;
+ unsigned int size() const;
+ private:
+ std::map<CStdString, JSONSchemaTypeDefinition> m_propertiesmap;
+ };
+
+ /*!
+ \brief List of properties of the parameter (only needed when the
+ parameter is an object)
+ */
+ CJsonSchemaPropertiesMap properties;
+ } JSONSchemaTypeDefinition;
+
+ /*!
+ \ingroup jsonrpc
+ \brief Structure for a published json
+ rpc method.
+
+ Represents a published json rpc method
+ and is used to verify an incoming json
+ rpc request against a defined method.
+ */
+ typedef struct
+ {
+ /*!
+ \brief Name of the represented method
+ */
+ const char* name;
+ /*!
+ \brief Pointer tot he implementation
+ of the represented method
+ */
+ MethodCall method;
+ /*!
+ \brief Definition of the type of
+ request/response
+ */
+ TransportLayerCapability transportneed;
+ /*!
+ \brief Definition of the permissions needed
+ to execute the method
+ */
+ OperationPermission permission;
+ /*!
+ \brief Whether this method changes the internal
+ state of XBMC or not
+ */
+ bool stateChanging;
+ /*!
+ \brief Description of the method
+ */
+ const char* description;
+ /*!
+ \brief List of accepted parameters
+ */
+ std::vector<JSONSchemaTypeDefinition> parameters;
+ /*!
+ \brief Definition of the return value
+ */
+ Json::Value returns;
+ } JsonRpcMethod;
+
+ /*!
+ \ingroup jsonrpc
+ \brief Structure mapping a json rpc method
+ definition to an actual method implementation.
+ */
+ typedef struct
+ {
+ /*!
+ \brief Name of the json rpc method.
+ */
+ const char* name;
+ /*!
+ \brief Pointer to the actual
+ implementation of the json rpc
+ method.
+ */
+ MethodCall method;
+ } JsonRpcMethodMap;
+
+ /*!
+ \ingroup jsonrpc
+ \brief Helper class for json schema service descriptor based
+ service descriptions for the json rpc API
+
+ Provides static functions to parse a complete json schema
+ service descriptor of a published service containing json rpc
+ methods, print the json schema service descriptor representation
+ into a string (mainly for output purposes) and evaluate and verify
+ parameters provided in a call to one of the publish json rpc methods
+ against a parameter definition parsed from a json schema service
+ descriptor.
+ */
+ class CJSONServiceDescription : public CJSONUtils
+ {
+ public:
+ /*!
+ \brief Parses the given json schema description and maps
+ the found methods against the given method map
+ \param serviceDescription json schema description to parse
+ \param methodMap Map of json rpc method names to actual C/C++ implementations
+ \param size Size of the method map
+ \return True if the json schema description has been parsed sucessfully otherwise false
+ */
+ static bool Parse(const JsonRpcMethodMap methodMap[], unsigned int size);
+
+ /*!
+ \brief Gets the version of the json
+ schema description
+ \return Version of the json schema description
+ */
+ static int GetVersion();
+
+ /*!
+ \brief Prints the json schema description into the given result object
+ \param result Object into which the json schema description is printed
+ \param transport Transport layer capabilities
+ \param client Client requesting a print
+ \param printDescriptions Whether to print descriptions or not
+ \param printMetadata Whether to print XBMC specific data or not
+ \param filterByTransport Whether to filter by transport or not
+ */
+ static void Print(Json::Value &result, ITransportLayer *transport, IClient *client, bool printDescriptions, bool printMetadata, bool filterByTransport);
+
+ /*!
+ \brief Checks the given parameters from the request against the
+ json schema description for the given method
+ \param method Called method
+ \param requestParameters Parameters from the request
+ \param client Client who sent the request
+ \param announcement Whether the request was sent as an announcement or not
+ \param methodCall Object which will contain the actual C/C++ method to be called
+ \param outputParameters Cleaned up parameter list
+ \return OK if the validation of the request succeeded otherwise an appropriate error code
+
+ Checks if the given method is a valid json rpc method, if the client has the permission
+ to call this method, if the method can be called as an announcement or not, assigns the
+ actual C/C++ implementation of the method to the "methodCall" parameter and checks the
+ given parameters from the request against the json schema description for the given method.
+ */
+ static JSON_STATUS CheckCall(const char* const method, const Json::Value &requestParameters, IClient *client, bool announcement, MethodCall &methodCall, Json::Value &outputParameters);
+
+ private:
+ static void printType(const JSONSchemaTypeDefinition &type, bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, Json::Value &output);
+ static JSON_STATUS checkParameter(const Json::Value &requestParameters, const JSONSchemaTypeDefinition &type, unsigned int position, Json::Value &outputParameters, unsigned int &handled, Json::Value &errorData);
+ static JSON_STATUS checkType(const Json::Value &value, const JSONSchemaTypeDefinition &type, Json::Value &outputValue, Json::Value &errorData);
+ static void parseHeader(const Json::Value &descriptionObject);
+ static bool parseMethod(const Json::Value &value, JsonRpcMethod &method);
+ static bool parseParameter(Json::Value &value, JSONSchemaTypeDefinition ¶meter);
+ static bool parseTypeDefinition(const Json::Value &value, JSONSchemaTypeDefinition &type, bool isParameter);
+ static void parseReturn(const Json::Value &value, Json::Value &returns);
+ static void addReferenceTypeDefinition(JSONSchemaTypeDefinition &typeDefinition);
+
+ class CJsonRpcMethodMap
+ {
+ public:
+ CJsonRpcMethodMap();
+
+ void add(JsonRpcMethod &method);
+
+ typedef std::map<CStdString, JsonRpcMethod>::const_iterator JsonRpcMethodIterator;
+ JsonRpcMethodIterator begin() const;
+ JsonRpcMethodIterator find(const CStdString& key) const;
+ JsonRpcMethodIterator end() const;
+ private:
+ std::map<CStdString, JsonRpcMethod> m_actionmap;
+ };
+
+ static Json::Value m_notifications;
+ static JsonRpcDescriptionHeader m_header;
+ static CJsonRpcMethodMap m_actionMap;
+ static std::map<CStdString, JSONSchemaTypeDefinition> m_types;
+ static std::vector<CStdString> m_badMethods;
+ static bool m_newReferenceType;
+ };
+}
*
*/
-#include "JSONRPC.h"
+#include <string.h>
+#include <stdlib.h>
+#include "utils/StdString.h"
+#include "interfaces/IAnnouncer.h"
+#include "ITransportLayer.h"
+
+using namespace ANNOUNCEMENT;
namespace JSONRPC
{
+ /*!
+ \ingroup jsonrpc
+ \brief Possible statuc codes of a response
+ to a JSON RPC request
+ */
+ enum JSON_STATUS
+ {
+ OK = 0,
+ ACK = -1,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ ParseError = -32700,
+ //-32099..-32000 Reserved for implementation-defined server-errors.
+ BadPermission = -32099,
+ FailedToExecute = -32100
+ };
+
+ /*!
+ \brief Function pointer for json rpc methods
+ */
+ typedef JSON_STATUS (*MethodCall) (const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
+
+ /*!
+ \ingroup jsonrpc
+ \brief Permission categories for json rpc methods
+
+ A json rpc method will only be called if the caller
+ has the correct permissions to exectue the method.
+ The method call needs to be perfectly threadsafe.
+ */
+ enum OperationPermission
+ {
+ ReadData = 0x1,
+ ControlPlayback = 0x2,
+ ControlAnnounce = 0x4,
+ ControlPower = 0x8,
+ Logging = 0x10,
+ ScanLibrary = 0x20,
+ };
+
+ static const int OPERATION_PERMISSION_ALL = (ReadData | ControlPlayback | ControlAnnounce | ControlPower | Logging | ScanLibrary);
+
+ /*!
+ \brief Possible value types of a parameter or return type
+ */
+ enum JSONSchemaType
+ {
+ NullValue = 0x01,
+ StringValue = 0x02,
+ NumberValue = 0x04,
+ IntegerValue = 0x08,
+ BooleanValue = 0x10,
+ ArrayValue = 0x20,
+ ObjectValue = 0x40,
+ AnyValue = 0x80
+ };
+
+ /*!
+ \ingroup jsonrpc
+ \brief Helper class containing utility methods to handle
+ json rpc method calls.*/
class CJSONUtils
{
protected:
- static inline bool ParameterIntOrNull(const Json::Value ¶meterObject, const char *key) { return parameterObject[key].isInt() || parameterObject[key].isNull(); }
- static inline int ParameterAsInt(const Json::Value ¶meterObject, int fallback, const char *key) { return parameterObject[key].isInt() ? parameterObject[key].asInt() : fallback; }
+ /*!
+ \brief Checks if the given object contains a parameter
+ \param parameterObject Object to check for a parameter
+ \param key Possible name of the parameter
+ \param position Possible position of the parameter
+ \return True if the parameter is available otherwise false
+
+ Checks the given object for a parameter with the given key (if
+ the given object is not an array) or for a parameter at the
+ given position (if the given object is an array).
+ */
+ static inline bool ParameterExists(const Json::Value ¶meterObject, const char *key, unsigned int position) { return IsValueMember(parameterObject, key) || (parameterObject.isArray() && parameterObject.size() > position); }
+
+ /*!
+ \brief Checks if the given object contains a value
+ with the given key
+ \param value Value to check for the member
+ \param key Key of the member to check for
+ \return True if the given object contains a member with
+ the given key otherwise false
+ */
+ static inline bool IsValueMember(const Json::Value &value, const char *key) { return value.isObject() && value.isMember(key); }
+
+ /*!
+ \brief Returns the json value of a parameter
+ \param parameterObject Object containing all provided parameters
+ \param key Possible name of the parameter
+ \param position Possible position of the parameter
+ \return Json value of the parameter with the given name or at the
+ given position
+
+ Returns the value of the parameter with the given key (if
+ the given object is not an array) or of the parameter at the
+ given position (if the given object is an array).
+ */
+ static inline Json::Value GetParameter(const Json::Value ¶meterObject, const char *key, unsigned int position) { return IsValueMember(parameterObject, key) ? parameterObject[key] : parameterObject[position]; }
+
+ /*!
+ \brief Returns the json value of a parameter or the given
+ default value
+ \param parameterObject Object containing all provided parameters
+ \param key Possible name of the parameter
+ \param position Possible position of the parameter
+ \param fallback Default value of the parameter
+ \return Json value of the parameter with the given name or at the
+ given position or the default value if the parameter does not exist
+
+ Returns the value of the parameter with the given key (if
+ the given object is not an array) or of the parameter at the
+ given position (if the given object is an array). If the
+ parameter does not exist the given default value is returned.
+ */
+ static inline Json::Value GetParameter(const Json::Value ¶meterObject, const char *key, unsigned int position, Json::Value fallback) { return IsValueMember(parameterObject, key) ? parameterObject[key] : ((parameterObject.isArray() && parameterObject.size() > position) ? parameterObject[position] : fallback); }
+
+ /*!
+ \brief Returns the given json value as a string
+ \param value Json value to convert to a string
+ \param defaultValue Default string value
+ \return String value of the given json value or the default value
+ if the given json value is no string
+ */
+ static inline const char* GetString(const Json::Value &value, const char* defaultValue)
+ {
+ CStdString str = defaultValue;
+ if (value.isString())
+ {
+ str = value.asString();
+ }
+
+ return strcpy((char *)malloc(sizeof(char) * str.length() + 1), str.c_str());
+ }
+
+ /*!
+ \brief Returns a string representation for the
+ given OperationPermission
+ \param permission Specific OperationPermission
+ \return String representation of the given OperationPermission
+ */
+ static inline const char* PermissionToString(const OperationPermission &permission)
+ {
+ switch (permission)
+ {
+ case ReadData:
+ return "ReadData";
+ case ControlPlayback:
+ return "ControlPlayback";
+ case ControlAnnounce:
+ return "ControlAnnounce";
+ case ControlPower:
+ return "ControlPower";
+ case Logging:
+ return "Logging";
+ case ScanLibrary:
+ return "ScanLibrary";
+ default:
+ return "Unknown";
+ }
+ }
+
+ /*!
+ \brief Returns a OperationPermission value for the given
+ string representation
+ \param permission String representation of the OperationPermission
+ \return OperationPermission value of the given string representation
+ */
+ static inline OperationPermission StringToPermission(const char *permission)
+ {
+ if (strcmp(permission, "ControlPlayback") == 0)
+ return ControlPlayback;
+ if (strcmp(permission, "ControlAnnounce") == 0)
+ return ControlAnnounce;
+ if (strcmp(permission, "ControlPower") == 0)
+ return ControlPower;
+ if (strcmp(permission, "Logging") == 0)
+ return Logging;
+ if (strcmp(permission, "ScanLibrary") == 0)
+ return ScanLibrary;
+
+ return ReadData;
+ }
+
+ /*!
+ \brief Returns a string representation for the
+ given EAnnouncementFlag
+ \param announcement Specific EAnnouncementFlag
+ \return String representation of the given EAnnouncementFlag
+ */
+ static inline const char* AnnouncementFlagToString(const EAnnouncementFlag &announcement)
+ {
+ switch (announcement)
+ {
+ case Playback:
+ return "Playback";
+ case GUI:
+ return "GUI";
+ case System:
+ return "System";
+ case Library:
+ return "Library";
+ case Other:
+ return "Other";
+ default:
+ return "Unknown";
+ }
+ }
+
+ /*!
+ \brief Returns a TransportLayerCapability value of the
+ given string representation
+ \param transport String representation of the TransportLayerCapability
+ \return TransportLayerCapability value of the given string representation
+ */
+ static inline TransportLayerCapability StringToTransportLayer(const char *transport)
+ {
+ if (strcmp(transport, "Announcing") == 0)
+ return Announcing;
+ if (strcmp(transport, "FileDownload") == 0)
+ return FileDownload;
+
+ return Response;
+ }
+
+ /*!
+ \brief Returns a JSONSchemaType value for the given
+ string representation
+ \param valueType String representation of the JSONSchemaType
+ \return JSONSchemaType value of the given string representation
+ */
+ static inline JSONSchemaType StringToSchemaValueType(const char *valueType)
+ {
+ if (strcmp(valueType, "null") == 0)
+ return NullValue;
+ if (strcmp(valueType, "string") == 0)
+ return StringValue;
+ if (strcmp(valueType, "number") == 0)
+ return NumberValue;
+ if (strcmp(valueType, "integer") == 0)
+ return IntegerValue;
+ if (strcmp(valueType, "boolean") == 0)
+ return BooleanValue;
+ if (strcmp(valueType, "array") == 0)
+ return ArrayValue;
+ if (strcmp(valueType, "object") == 0)
+ return ObjectValue;
+
+ return AnyValue;
+ }
+
+ /*!
+ \brief Returns a string representation for the
+ given JSONSchemaType
+ \param valueType Specific JSONSchemaType
+ \return String representation of the given JSONSchemaType
+ */
+ static inline const char* SchemaValueTypeToString(JSONSchemaType valueType)
+ {
+ std::vector<JSONSchemaType> types = std::vector<JSONSchemaType>();
+ for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2)
+ {
+ if (HasType(valueType, (JSONSchemaType)value))
+ types.push_back((JSONSchemaType)value);
+ }
+
+ CStdString strType;
+ if (types.size() > 1)
+ strType.append("[");
+
+ for (unsigned int index = 0; index < types.size(); index++)
+ {
+ if (index > 0)
+ strType.append(", ");
+
+ switch (types.at(index))
+ {
+ case StringValue:
+ strType.append("string");
+ break;
+ case NumberValue:
+ strType.append("number");
+ break;
+ case IntegerValue:
+ strType.append("integer");
+ break;
+ case BooleanValue:
+ strType.append("boolean");
+ break;
+ case ArrayValue:
+ strType.append("array");
+ break;
+ case ObjectValue:
+ strType.append("object");
+ break;
+ case AnyValue:
+ strType.append("any");
+ break;
+ case NullValue:
+ strType.append("null");
+ break;
+ default:
+ strType.append("unknown");
+ }
+ }
+
+ if (types.size() > 1)
+ strType.append("]");
+
+ return strcpy((char *)malloc(sizeof(char) * strType.length() + 1), strType.c_str());
+ }
+
+ /*!
+ \brief Converts the given json schema type into
+ a json object
+ \param valueTye json schema type(s)
+ \param jsonObject json object into which the json schema type(s) are stored
+ */
+ static inline void SchemaValueTypeToJson(JSONSchemaType valueType, Json::Value &jsonObject)
+ {
+ jsonObject = Json::Value(Json::arrayValue);
+ for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2)
+ {
+ if (HasType(valueType, (JSONSchemaType)value))
+ jsonObject.append(SchemaValueTypeToString((JSONSchemaType)value));
+ }
+
+ if (jsonObject.size() == 1)
+ jsonObject = jsonObject[0];
+ }
+
+ static inline const char* ValueTypeToString(Json::ValueType valueType)
+ {
+ switch (valueType)
+ {
+ case Json::stringValue:
+ return "string";
+ case Json::realValue:
+ return "number";
+ case Json::intValue:
+ case Json::uintValue:
+ return "integer";
+ case Json::booleanValue:
+ return "boolean";
+ case Json::arrayValue:
+ return "array";
+ case Json::objectValue:
+ return "object";
+ case Json::nullValue:
+ return "null";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*!
+ \brief Checks if the parameter with the given name or at
+ the given position is of a certain type
+ \param parameterObject Object containing all provided parameters
+ \param key Possible name of the parameter
+ \param position Possible position of the parameter
+ \param valueType Expected type of the parameter
+ \return True if the specific parameter is of the given type otherwise false
+ */
+ static inline bool IsParameterType(const Json::Value ¶meterObject, const char *key, unsigned int position, JSONSchemaType valueType)
+ {
+ if ((valueType & AnyValue) == AnyValue)
+ return true;
+
+ Json::Value parameter;
+ if (IsValueMember(parameterObject, key))
+ parameter = parameterObject[key];
+ else if(parameterObject.isArray() && parameterObject.size() > position)
+ parameter = parameterObject[position];
+
+ return IsType(parameter, valueType);
+ }
+
+ /*!
+ \brief Checks if the given json value is of the given type
+ \param value Json value to check
+ \param valueType Expected type of the json value
+ \return True if the given json value is of the given type otherwise false
+ */
+ static inline bool IsType(const Json::Value &value, JSONSchemaType valueType)
+ {
+ if (HasType(valueType, AnyValue))
+ return true;
+ if (HasType(valueType, StringValue) && value.isString())
+ return true;
+ if (HasType(valueType, NumberValue) && (value.isInt() || value.isUInt() || value.isDouble()))
+ return true;
+ if (HasType(valueType, IntegerValue) && (value.isInt() || value.isUInt()))
+ return true;
+ if (HasType(valueType, BooleanValue) && value.isBool())
+ return true;
+ if (HasType(valueType, ArrayValue) && value.isArray())
+ return true;
+ if (HasType(valueType, ObjectValue) && value.isObject())
+ return true;
+
+ return value.isNull();
+ }
+
+ /*!
+ \brief Sets the value of the given json value to the
+ default value of the given type
+ \param value Json value to be set
+ \param valueType Type of the default value
+ */
+ static inline void SetDefaultValue(Json::Value &value, JSONSchemaType valueType)
+ {
+ switch (valueType)
+ {
+ case StringValue:
+ value = Json::Value("");
+ break;
+ case NumberValue:
+ value = Json::Value(Json::realValue);
+ break;
+ case IntegerValue:
+ value = Json::Value(Json::intValue);
+ break;
+ case BooleanValue:
+ value = Json::Value(Json::booleanValue);
+ break;
+ case ArrayValue:
+ value = Json::Value(Json::arrayValue);
+ break;
+ case ObjectValue:
+ value = Json::Value(Json::objectValue);
+ break;
+ default:
+ value = Json::Value(Json::nullValue);
+ }
+ }
+
+ static inline bool HasType(JSONSchemaType typeObject, JSONSchemaType type) { return (typeObject & type) == type; }
+
+ static inline int Compare(const Json::Value &value, const Json::Value &other)
+ {
+ if (value.type() != other.type())
+ return other.type() - value.type();
+
+ int result = 0;
+ Json::Value::Members members, otherMembers;
+
+ switch (value.type())
+ {
+ case Json::nullValue:
+ return 0;
+ case Json::intValue:
+ return other.asInt() - value.asInt();
+ case Json::uintValue:
+ return other.asUInt() - value.asUInt();
+ case Json::realValue:
+ return (int)(other.asDouble() - value.asDouble());
+ case Json::booleanValue:
+ return other.asBool() - value.asBool();
+ case Json::stringValue:
+ return other.asString().compare(value.asString());
+ case Json::arrayValue:
+ if (other.size() != value.size())
+ return other.size() - value.size();
+
+ for (unsigned int i = 0; i < other.size(); i++)
+ {
+ if ((result = value.get(i, NULL).compare(other[i])) != 0)
+ return result;
+ }
+
+ return 0;
+ case Json::objectValue:
+ members = value.getMemberNames();
+ otherMembers = other.getMemberNames();
+
+ if (members.size() != otherMembers.size())
+ return otherMembers.size() - members.size();
+
+ for (unsigned int i = 0; i < members.size(); i++)
+ {
+ if (!other.isMember(members.at(i)))
+ return -1;
+
+ if ((result = value.get(members.at(i), NULL).compare(other[members.at(i)])) != 0)
+ return result;
+ }
+
+ return 0;
+ }
- static inline const Json::Value ForceObject(const Json::Value &value) { return value.isObject() ? value : Json::Value(Json::objectValue); }
+ return -1; // unreachable
+ }
};
}
FileItemHandler.cpp \
FileOperations.cpp \
JSONRPC.cpp \
+ JSONServiceDescription.cpp \
PicturePlayerOperations.cpp \
PlayerOperations.cpp \
PlaylistOperations.cpp \
JSON_STATUS CPlaylistOperations::Add(const CStdString &method, ITransportLayer *transport, IClient *client, const Value ¶meterObject, Value &result)
{
- Value param = ForceObject(parameterObject);
-
CSingleLock lock(VirtualCriticalSection);
- CPlayListPtr playlist = GetPlaylist(param);
- param.removeMember(PLAYLIST_MEMBER_VIRTUAL);
+ CPlayListPtr playlist = GetPlaylist(parameterObject);
+ //parameterObject.removeMember(PLAYLIST_MEMBER_VIRTUAL);
if (playlist)
{
CFileItemList list;
- if (CFileItemHandler::FillFileItemList(param, list) && list.Size() > 0)
+ if (CFileItemHandler::FillFileItemList(parameterObject, list) && list.Size() > 0)
playlist->Add(list);
return ACK;
JSON_STATUS CPlaylistOperations::Remove(const CStdString &method, ITransportLayer *transport, IClient *client, const Value ¶meterObject, Value &result)
{
- const Value param = ForceObject(parameterObject);
- if (!(param["item"].isInt() || param["item"].isString()))
+ if (!(parameterObject["item"].isInt() || parameterObject["item"].isString()))
return InvalidParams;
CSingleLock lock(VirtualCriticalSection);
- CPlayListPtr playlist = GetPlaylist(param);
+ CPlayListPtr playlist = GetPlaylist(parameterObject);
if (playlist)
{
- if (param["item"].isInt())
- playlist->Remove(param["item"].asInt());
- else if (param["item"].isString())
- playlist->Remove(param["item"].asString());
+ if (parameterObject["item"].isInt())
+ playlist->Remove(parameterObject["item"].asInt());
+ else if (parameterObject["item"].isString())
+ playlist->Remove(parameterObject["item"].asString());
return ACK;
}
JSON_STATUS CPlaylistOperations::Swap(const CStdString &method, ITransportLayer *transport, IClient *client, const Value ¶meterObject, Value &result)
{
- const Value param = ForceObject(parameterObject);
- if (!param["item1"].isInt() && !param["item2"].isInt())
+ if (!parameterObject["item1"].isInt() && !parameterObject["item2"].isInt())
return InvalidParams;
CSingleLock lock(VirtualCriticalSection);
- CPlayListPtr playlist = GetPlaylist(param);
+ CPlayListPtr playlist = GetPlaylist(parameterObject);
- if (playlist && playlist->Swap(param["item1"].asInt(), param["item2"].asInt()))
+ if (playlist && playlist->Swap(parameterObject["item1"].asInt(), parameterObject["item2"].asInt()))
return ACK;
return InvalidParams;
CPlayListPtr CPlaylistOperations::GetPlaylist(const Value ¶meterObject)
{
- const Value param = ForceObject(parameterObject);
- if (param[PLAYLIST_MEMBER_VIRTUAL].isString())
+ if (parameterObject[PLAYLIST_MEMBER_VIRTUAL].isString())
{
- CStdString id = param[PLAYLIST_MEMBER_VIRTUAL].asString();
+ CStdString id = parameterObject[PLAYLIST_MEMBER_VIRTUAL].asString();
return VirtualPlaylists[id];
}
- else if (param[PLAYLIST_MEMBER_FILE].isString())
+ else if (parameterObject[PLAYLIST_MEMBER_FILE].isString())
{
- CStdString file = param[PLAYLIST_MEMBER_FILE].asString();
+ CStdString file = parameterObject[PLAYLIST_MEMBER_FILE].asString();
CPlayListPtr playlist = CPlayListPtr(CPlayListFactory::Create(file));
if (playlist && playlist->Load(file))
return playlist;
--- /dev/null
+#pragma once
+/*
+ * Copyright (C) 2005-2010 Team XBMC
+ * http://www.xbmc.org
+ *
+ * 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
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+namespace JSONRPC
+{
+ const char* const JSON_SERVICE_DESCRIPTION =
+ "{"
+ "\"id\": \"http://www.xbmc.org/jsonrpc.jsd\","
+ "\"version\": 3,"
+ "\"description\": \"JSON RPC API of XBMC\","
+
+ "\"JSONRPC.Introspect\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Enumerates all actions and descriptions\","
+ "\"transport\": \"Response\","
+ "\"permission\": \"ReadData\","
+ "\"statechanging\": false,"
+ "\"params\": ["
+ "{ \"name\": \"getdescriptions\", \"type\": \"boolean\", \"default\": true },"
+ "{ \"name\": \"getmetadata\", \"type\": \"boolean\", \"default\": false },"
+ "{ \"name\": \"filterbytransport\", \"type\": \"boolean\", \"default\": true }"
+ "],"
+ "\"returns\": \"object\""
+ "},"
+ "\"JSONRPC.Version\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Retrieve the jsonrpc protocol version\","
+ "\"transport\": \"Response\","
+ "\"permission\": \"ReadData\","
+ "\"statechanging\": false,"
+ "\"params\": [],"
+ "\"returns\": \"string\""
+ "},"
+ "\"JSONRPC.Permission\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Retrieve the clients permissions\","
+ "\"transport\": \"Response\","
+ "\"permission\": \"ReadData\","
+ "\"statechanging\": false,"
+ "\"params\": [],"
+ "\"returns\": {"
+ "\"type\": \"object\","
+ "\"properties\": {"
+ "\"ReadData\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"ControlPlayback\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"ControlAnnounce\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"ControlPower\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"Logging\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"ScanLibrary\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" }"
+ "}"
+ "}"
+ "},"
+ "\"JSONRPC.Ping\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Ping responder\","
+ "\"transport\": \"Response\","
+ "\"permission\": \"ReadData\","
+ "\"statechanging\": false,"
+ "\"params\": [],"
+ "\"returns\": \"string\""
+ "},"
+ "\"JSONRPC.GetAnnouncementFlags\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Get announcement flags\","
+ "\"transport\": \"Announcing\","
+ "\"permission\": \"ReadData\","
+ "\"statechanging\": false,"
+ "\"params\": [],"
+ "\"returns\": {"
+ "\"id\": \"Announcement.Flags\","
+ "\"type\": \"object\","
+ "\"properties\": {"
+ "\"Playback\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"GUI\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"System\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"Library\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+ "\"Other\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" }"
+ "}"
+ "}"
+ "},"
+ "\"JSONRPC.SetAnnouncementFlags\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Change the announcement flags\","
+ "\"transport\": \"Announcing\","
+ "\"permission\": \"ControlAnnounce\","
+ "\"statechanging\": true,"
+ "\"params\": ["
+ "{ \"name\": \"Playback\", \"type\": \"boolean\", \"default\": false },"
+ "{ \"name\": \"GUI\", \"type\": \"boolean\", \"default\": false },"
+ "{ \"name\": \"System\", \"type\": \"boolean\", \"default\": false },"
+ "{ \"name\": \"Library\", \"type\": \"boolean\", \"default\": false },"
+ "{ \"name\": \"Other\", \"type\": \"boolean\", \"default\": false }"
+ "],"
+ "\"returns\": { \"$ref\": \"Announcement.Flags\" }"
+ "},"
+ "\"JSONRPC.Announce\": {"
+ "\"type\": \"method\","
+ "\"description\": \"Announce to other connected clients\","
+ "\"transport\": \"Response\","
+ "\"permission\": \"ReadData\","
+ "\"statechanging\": false,"
+ "\"params\": ["
+ "{ \"name\": \"sender\", \"type\": \"string\", \"required\": true },"
+ "{ \"name\": \"message\", \"type\": \"string\", \"required\": true },"
+ "{ \"name\": \"data\", \"type\": \"any\", \"default\": null }"
+ "],"
+ "\"returns\": \"any\""
+ "}"
+ "}";
+
+ const char* const JSON_NOTIFICATION_DESCRIPTION =
+ "{"
+
+ "}";
+}
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!ParameterIntOrNull(param, "tvshowid"))
- return InvalidParams;
+ //if (!ParameterIntOrNull(parameterObject, "tvshowid"))
+ // return InvalidParams;
- int tvshowID = ParameterAsInt(param, -1, "tvshowid");
+ int tvshowID = parameterObject.get("tvshowid", -1).asInt();
CVideoDatabase videodatabase;
if (!videodatabase.Open())
CFileItemList items;
if (videodatabase.GetSeasonsNav("videodb://", items, -1, -1, -1, -1, tvshowID))
- HandleFileItemList(NULL, false, "seasons", items, param, result);
+ HandleFileItemList(NULL, false, "seasons", items, parameterObject, result);
videodatabase.Close();
return OK;
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!(ParameterIntOrNull(param, "tvshowid") || ParameterIntOrNull(param, "season")))
- return InvalidParams;
+ //if (!(ParameterIntOrNull(parameterObject, "tvshowid") || ParameterIntOrNull(parameterObject, "season")))
+ // return InvalidParams;
- int tvshowID = ParameterAsInt(param, -1, "tvshowid");
- int season = ParameterAsInt(param, -1, "season");
+ int tvshowID = parameterObject.get("tvshowid", -1).asInt();
+ int season = parameterObject.get("season", -1).asInt();
CVideoDatabase videodatabase;
if (!videodatabase.Open())
CFileItemList items;
if (videodatabase.GetEpisodesNav("videodb://", items, -1, -1, -1, -1, tvshowID, season))
- HandleFileItemList("episodeid", true, "episodes", items, param, result);
+ HandleFileItemList("episodeid", true, "episodes", items, parameterObject, result);
videodatabase.Close();
return OK;
if (!(parameterObject.isObject() || parameterObject.isNull()))
return InvalidParams;
- const Value param = ForceObject(parameterObject);
- if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "albumid")))
- return InvalidParams;
+ //if (!(ParameterIntOrNull(parameterObject, "artistid") || ParameterIntOrNull(parameterObject, "albumid")))
+ // return InvalidParams;
- int artistID = ParameterAsInt(param, -1, "artistid");
- int albumID = ParameterAsInt(param, -1, "albumid");
+ int artistID = parameterObject.get("artistid", -1).asInt();
+ int albumID = parameterObject.get("albumid", -1).asInt();
CVideoDatabase videodatabase;
if (!videodatabase.Open())
CFileItemList items;
if (videodatabase.GetMusicVideosNav("videodb://", items, -1, -1, artistID, -1, -1, albumID))
- HandleFileItemList("musicvideoid", true, "musicvideos", items, param, result);
+ HandleFileItemList("musicvideoid", true, "musicvideos", items, parameterObject, result);
videodatabase.Close();
return OK;
CVideoDatabase videodatabase;
if ((parameterObject["movieid"].isInt() || parameterObject["episodeid"].isInt() || parameterObject["musicvideoid"].isInt()) && videodatabase.Open())
{
- int movieID = ParameterAsInt(parameterObject, -1, "movieid");
- int episodeID = ParameterAsInt(parameterObject, -1, "episodeid");
- int musicVideoID = ParameterAsInt(parameterObject, -1, "musicvideoid");
+ int movieID = parameterObject.get("movieid", -1).asInt();
+ int episodeID = parameterObject.get("episodeid", -1).asInt();
+ int musicVideoID = parameterObject.get("musicvideoid", -1).asInt();
bool success = true;
if (movieID > 0)