/* * Copyright (C) 2016-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "JSONServiceDescription.h" #include "AddonsOperations.h" #include "ApplicationOperations.h" #include "AudioLibrary.h" #include "FavouritesOperations.h" #include "FileOperations.h" #include "GUIOperations.h" #include "InputOperations.h" #include "JSONRPC.h" #include "PVROperations.h" #include "PlayerOperations.h" #include "PlaylistOperations.h" #include "ProfilesOperations.h" #include "ServiceDescription.h" #include "SettingsOperations.h" #include "SystemOperations.h" #include "TextureOperations.h" #include "VideoLibrary.h" #include "XBMCOperations.h" #include "utils/JSONVariantParser.h" #include "utils/StringUtils.h" #include "utils/log.h" using namespace JSONRPC; std::map CJSONServiceDescription::m_notifications = std::map(); CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap; std::map CJSONServiceDescription::m_types = std::map(); CJSONServiceDescription::IncompleteSchemaDefinitionMap CJSONServiceDescription::m_incompleteDefinitions = CJSONServiceDescription::IncompleteSchemaDefinitionMap(); // clang-format off JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = { // JSON-RPC { "JSONRPC.Introspect", CJSONRPC::Introspect }, { "JSONRPC.Version", CJSONRPC::Version }, { "JSONRPC.Permission", CJSONRPC::Permission }, { "JSONRPC.Ping", CJSONRPC::Ping }, { "JSONRPC.GetConfiguration", CJSONRPC::GetConfiguration }, { "JSONRPC.SetConfiguration", CJSONRPC::SetConfiguration }, { "JSONRPC.NotifyAll", CJSONRPC::NotifyAll }, // Player { "Player.GetActivePlayers", CPlayerOperations::GetActivePlayers }, { "Player.GetPlayers", CPlayerOperations::GetPlayers }, { "Player.GetProperties", CPlayerOperations::GetProperties }, { "Player.GetItem", CPlayerOperations::GetItem }, { "Player.PlayPause", CPlayerOperations::PlayPause }, { "Player.Stop", CPlayerOperations::Stop }, { "Player.GetAudioDelay", CPlayerOperations::GetAudioDelay }, { "Player.SetAudioDelay", CPlayerOperations::SetAudioDelay }, { "Player.SetSpeed", CPlayerOperations::SetSpeed }, { "Player.Seek", CPlayerOperations::Seek }, { "Player.Move", CPlayerOperations::Move }, { "Player.Zoom", CPlayerOperations::Zoom }, { "Player.SetViewMode", CPlayerOperations::SetViewMode }, { "Player.GetViewMode", CPlayerOperations::GetViewMode }, { "Player.Rotate", CPlayerOperations::Rotate }, { "Player.Open", CPlayerOperations::Open }, { "Player.GoTo", CPlayerOperations::GoTo }, { "Player.SetShuffle", CPlayerOperations::SetShuffle }, { "Player.SetRepeat", CPlayerOperations::SetRepeat }, { "Player.SetPartymode", CPlayerOperations::SetPartymode }, { "Player.SetAudioStream", CPlayerOperations::SetAudioStream }, { "Player.AddSubtitle", CPlayerOperations::AddSubtitle }, { "Player.SetSubtitle", CPlayerOperations::SetSubtitle }, { "Player.SetVideoStream", CPlayerOperations::SetVideoStream }, // Playlist { "Playlist.GetPlaylists", CPlaylistOperations::GetPlaylists }, { "Playlist.GetProperties", CPlaylistOperations::GetProperties }, { "Playlist.GetItems", CPlaylistOperations::GetItems }, { "Playlist.Add", CPlaylistOperations::Add }, { "Playlist.Insert", CPlaylistOperations::Insert }, { "Playlist.Clear", CPlaylistOperations::Clear }, { "Playlist.Remove", CPlaylistOperations::Remove }, { "Playlist.Swap", CPlaylistOperations::Swap }, // Files { "Files.GetSources", CFileOperations::GetRootDirectory }, { "Files.GetDirectory", CFileOperations::GetDirectory }, { "Files.GetFileDetails", CFileOperations::GetFileDetails }, { "Files.SetFileDetails", CFileOperations::SetFileDetails }, { "Files.PrepareDownload", CFileOperations::PrepareDownload }, { "Files.Download", CFileOperations::Download }, // Music Library { "AudioLibrary.GetProperties", CAudioLibrary::GetProperties }, { "AudioLibrary.GetArtists", CAudioLibrary::GetArtists }, { "AudioLibrary.GetArtistDetails", CAudioLibrary::GetArtistDetails }, { "AudioLibrary.GetAlbums", CAudioLibrary::GetAlbums }, { "AudioLibrary.GetAlbumDetails", CAudioLibrary::GetAlbumDetails }, { "AudioLibrary.GetSongs", CAudioLibrary::GetSongs }, { "AudioLibrary.GetSongDetails", CAudioLibrary::GetSongDetails }, { "AudioLibrary.GetRecentlyAddedAlbums", CAudioLibrary::GetRecentlyAddedAlbums }, { "AudioLibrary.GetRecentlyAddedSongs", CAudioLibrary::GetRecentlyAddedSongs }, { "AudioLibrary.GetRecentlyPlayedAlbums", CAudioLibrary::GetRecentlyPlayedAlbums }, { "AudioLibrary.GetRecentlyPlayedSongs", CAudioLibrary::GetRecentlyPlayedSongs }, { "AudioLibrary.GetGenres", CAudioLibrary::GetGenres }, { "AudioLibrary.GetRoles", CAudioLibrary::GetRoles }, { "AudioLibrary.GetSources", CAudioLibrary::GetSources }, { "AudioLibrary.GetAvailableArtTypes", CAudioLibrary::GetAvailableArtTypes }, { "AudioLibrary.GetAvailableArt", CAudioLibrary::GetAvailableArt }, { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails }, { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails }, { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails }, { "AudioLibrary.Scan", CAudioLibrary::Scan }, { "AudioLibrary.Export", CAudioLibrary::Export }, { "AudioLibrary.Clean", CAudioLibrary::Clean }, // Video Library { "VideoLibrary.GetGenres", CVideoLibrary::GetGenres }, { "VideoLibrary.GetTags", CVideoLibrary::GetTags }, { "VideoLibrary.GetAvailableArtTypes", CVideoLibrary::GetAvailableArtTypes }, { "VideoLibrary.GetAvailableArt", CVideoLibrary::GetAvailableArt }, { "VideoLibrary.GetMovies", CVideoLibrary::GetMovies }, { "VideoLibrary.GetMovieDetails", CVideoLibrary::GetMovieDetails }, { "VideoLibrary.GetMovieSets", CVideoLibrary::GetMovieSets }, { "VideoLibrary.GetMovieSetDetails", CVideoLibrary::GetMovieSetDetails }, { "VideoLibrary.GetTVShows", CVideoLibrary::GetTVShows }, { "VideoLibrary.GetTVShowDetails", CVideoLibrary::GetTVShowDetails }, { "VideoLibrary.GetSeasons", CVideoLibrary::GetSeasons }, { "VideoLibrary.GetSeasonDetails", CVideoLibrary::GetSeasonDetails }, { "VideoLibrary.GetEpisodes", CVideoLibrary::GetEpisodes }, { "VideoLibrary.GetEpisodeDetails", CVideoLibrary::GetEpisodeDetails }, { "VideoLibrary.GetMusicVideos", CVideoLibrary::GetMusicVideos }, { "VideoLibrary.GetMusicVideoDetails", CVideoLibrary::GetMusicVideoDetails }, { "VideoLibrary.GetRecentlyAddedMovies", CVideoLibrary::GetRecentlyAddedMovies }, { "VideoLibrary.GetRecentlyAddedEpisodes", CVideoLibrary::GetRecentlyAddedEpisodes }, { "VideoLibrary.GetRecentlyAddedMusicVideos", CVideoLibrary::GetRecentlyAddedMusicVideos }, { "VideoLibrary.GetInProgressTVShows", CVideoLibrary::GetInProgressTVShows }, { "VideoLibrary.SetMovieDetails", CVideoLibrary::SetMovieDetails }, { "VideoLibrary.SetMovieSetDetails", CVideoLibrary::SetMovieSetDetails }, { "VideoLibrary.SetTVShowDetails", CVideoLibrary::SetTVShowDetails }, { "VideoLibrary.SetSeasonDetails", CVideoLibrary::SetSeasonDetails }, { "VideoLibrary.SetEpisodeDetails", CVideoLibrary::SetEpisodeDetails }, { "VideoLibrary.SetMusicVideoDetails", CVideoLibrary::SetMusicVideoDetails }, { "VideoLibrary.RefreshMovie", CVideoLibrary::RefreshMovie }, { "VideoLibrary.RefreshTVShow", CVideoLibrary::RefreshTVShow }, { "VideoLibrary.RefreshEpisode", CVideoLibrary::RefreshEpisode }, { "VideoLibrary.RefreshMusicVideo", CVideoLibrary::RefreshMusicVideo }, { "VideoLibrary.RemoveMovie", CVideoLibrary::RemoveMovie }, { "VideoLibrary.RemoveTVShow", CVideoLibrary::RemoveTVShow }, { "VideoLibrary.RemoveEpisode", CVideoLibrary::RemoveEpisode }, { "VideoLibrary.RemoveMusicVideo", CVideoLibrary::RemoveMusicVideo }, { "VideoLibrary.Scan", CVideoLibrary::Scan }, { "VideoLibrary.Export", CVideoLibrary::Export }, { "VideoLibrary.Clean", CVideoLibrary::Clean }, // Addon operations { "Addons.GetAddons", CAddonsOperations::GetAddons }, { "Addons.GetAddonDetails", CAddonsOperations::GetAddonDetails }, { "Addons.SetAddonEnabled", CAddonsOperations::SetAddonEnabled }, { "Addons.ExecuteAddon", CAddonsOperations::ExecuteAddon }, // GUI operations { "GUI.GetProperties", CGUIOperations::GetProperties }, { "GUI.ActivateWindow", CGUIOperations::ActivateWindow }, { "GUI.ShowNotification", CGUIOperations::ShowNotification }, { "GUI.SetFullscreen", CGUIOperations::SetFullscreen }, { "GUI.SetStereoscopicMode", CGUIOperations::SetStereoscopicMode }, { "GUI.GetStereoscopicModes", CGUIOperations::GetStereoscopicModes }, // PVR operations { "PVR.GetProperties", CPVROperations::GetProperties }, { "PVR.GetChannelGroups", CPVROperations::GetChannelGroups }, { "PVR.GetChannelGroupDetails", CPVROperations::GetChannelGroupDetails }, { "PVR.GetChannels", CPVROperations::GetChannels }, { "PVR.GetChannelDetails", CPVROperations::GetChannelDetails }, { "PVR.GetClients", CPVROperations::GetClients }, { "PVR.GetBroadcasts", CPVROperations::GetBroadcasts }, { "PVR.GetBroadcastDetails", CPVROperations::GetBroadcastDetails }, { "PVR.GetBroadcastIsPlayable", CPVROperations::GetBroadcastIsPlayable }, { "PVR.GetTimers", CPVROperations::GetTimers }, { "PVR.GetTimerDetails", CPVROperations::GetTimerDetails }, { "PVR.GetRecordings", CPVROperations::GetRecordings }, { "PVR.GetRecordingDetails", CPVROperations::GetRecordingDetails }, { "PVR.AddTimer", CPVROperations::AddTimer }, { "PVR.DeleteTimer", CPVROperations::DeleteTimer }, { "PVR.ToggleTimer", CPVROperations::ToggleTimer }, { "PVR.Record", CPVROperations::Record }, { "PVR.Scan", CPVROperations::Scan }, // Profiles operations { "Profiles.GetProfiles", CProfilesOperations::GetProfiles}, { "Profiles.GetCurrentProfile", CProfilesOperations::GetCurrentProfile}, { "Profiles.LoadProfile", CProfilesOperations::LoadProfile}, // System operations { "System.GetProperties", CSystemOperations::GetProperties }, { "System.EjectOpticalDrive", CSystemOperations::EjectOpticalDrive }, { "System.Shutdown", CSystemOperations::Shutdown }, { "System.Suspend", CSystemOperations::Suspend }, { "System.Hibernate", CSystemOperations::Hibernate }, { "System.Reboot", CSystemOperations::Reboot }, // Input operations { "Input.SendText", CInputOperations::SendText }, { "Input.ExecuteAction", CInputOperations::ExecuteAction }, { "Input.ButtonEvent", CInputOperations::ButtonEvent }, { "Input.Left", CInputOperations::Left }, { "Input.Right", CInputOperations::Right }, { "Input.Down", CInputOperations::Down }, { "Input.Up", CInputOperations::Up }, { "Input.Select", CInputOperations::Select }, { "Input.Back", CInputOperations::Back }, { "Input.ContextMenu", CInputOperations::ContextMenu }, { "Input.Info", CInputOperations::Info }, { "Input.Home", CInputOperations::Home }, { "Input.ShowCodec", CInputOperations::ShowCodec }, { "Input.ShowOSD", CInputOperations::ShowOSD }, { "Input.ShowPlayerProcessInfo", CInputOperations::ShowPlayerProcessInfo }, // Application operations { "Application.GetProperties", CApplicationOperations::GetProperties }, { "Application.SetVolume", CApplicationOperations::SetVolume }, { "Application.SetMute", CApplicationOperations::SetMute }, { "Application.Quit", CApplicationOperations::Quit }, // Favourites operations { "Favourites.GetFavourites", CFavouritesOperations::GetFavourites }, { "Favourites.AddFavourite", CFavouritesOperations::AddFavourite }, // Textures operations { "Textures.GetTextures", CTextureOperations::GetTextures }, { "Textures.RemoveTexture", CTextureOperations::RemoveTexture }, // Settings operations { "Settings.GetSections", CSettingsOperations::GetSections }, { "Settings.GetCategories", CSettingsOperations::GetCategories }, { "Settings.GetSettings", CSettingsOperations::GetSettings }, { "Settings.GetSettingValue", CSettingsOperations::GetSettingValue }, { "Settings.SetSettingValue", CSettingsOperations::SetSettingValue }, { "Settings.ResetSettingValue", CSettingsOperations::ResetSettingValue }, { "Settings.GetSkinSettings", CSettingsOperations::GetSkinSettings }, { "Settings.GetSkinSettingValue", CSettingsOperations::GetSkinSettingValue }, { "Settings.SetSkinSettingValue", CSettingsOperations::SetSkinSettingValue }, // XBMC operations { "XBMC.GetInfoLabels", CXBMCOperations::GetInfoLabels }, { "XBMC.GetInfoBooleans", CXBMCOperations::GetInfoBooleans } }; // clang-format on JSONSchemaTypeDefinition::JSONSchemaTypeDefinition() : missingReference(), name(), ID(), referencedType(nullptr), extends(), description(), unionTypes(), defaultValue(), minimum(-std::numeric_limits::max()), maximum(std::numeric_limits::max()), enums(), items(), additionalItems(), properties(), additionalProperties(nullptr) { } bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* = false */) { bool hasReference = false; // 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 std::string refType = value["$ref"].asString(); // Check if the referenced type exists JSONSchemaTypeDefinitionPtr referencedTypeDef = CJSONServiceDescription::GetType(refType); if (refType.length() <= 0 || referencedTypeDef.get() == NULL) { CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} references an unknown type {}", name, refType); missingReference = refType; return false; } std::string typeName = name; *this = *referencedTypeDef; if (!typeName.empty()) name = typeName; referencedType = referencedTypeDef; hasReference = true; } else if (value.isMember("id") && value["id"].isString()) ID = GetString(value["id"], ""); // Check if the "required" field has been defined optional = value.isMember("required") && value["required"].isBoolean() ? !value["required"].asBoolean() : true; // Get the "description" if (!hasReference || (value.isMember("description") && value["description"].isString())) description = GetString(value["description"], ""); if (hasReference) { // If there is a specific default value, read it if (value.isMember("default") && IsType(value["default"], type)) { bool ok = false; if (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 (const auto& itr : enums) { if (value["default"] == itr) { ok = true; break; } } } if (ok) defaultValue = value["default"]; } return true; } // Check whether this type extends an existing type if (value.isMember("extends")) { if (value["extends"].isString()) { std::string extendsName = GetString(value["extends"], ""); if (!extendsName.empty()) { JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName); if (extendedTypeDef.get() == NULL) { CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name, extendsName); missingReference = extendsName; return false; } type = extendedTypeDef->type; extends.push_back(extendedTypeDef); } } else if (value["extends"].isArray()) { JSONSchemaType extendedType = AnyValue; for (unsigned int extendsIndex = 0; extendsIndex < value["extends"].size(); extendsIndex++) { std::string extendsName = GetString(value["extends"][extendsIndex], ""); if (!extendsName.empty()) { JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName); if (extendedTypeDef.get() == NULL) { extends.clear(); CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name, extendsName); missingReference = extendsName; return false; } if (extendsIndex == 0) extendedType = extendedTypeDef->type; else if (extendedType != extendedTypeDef->type) { extends.clear(); CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends multiple JSON schema types of " "mismatching types", name); return false; } extends.push_back(extendedTypeDef); } } type = extendedType; } } // Only read the "type" attribute if it's // not an extending type if (extends.size() <= 0) { // Get the defined type of the parameter if (!CJSONServiceDescription::parseJSONSchemaType(value["type"], unionTypes, type, missingReference)) return false; } if (HasType(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 for (CVariant::const_iterator_map itr = value["properties"].begin_map(); itr != value["properties"].end_map(); ++itr) { // 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 JSONSchemaTypeDefinitionPtr propertyType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); propertyType->name = itr->first; if (!propertyType->Parse(itr->second)) { missingReference = propertyType->missingReference; return false; } defaultValue[itr->first] = propertyType->defaultValue; properties.add(propertyType); } } hasAdditionalProperties = true; additionalProperties = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); if (value.isMember("additionalProperties")) { if (value["additionalProperties"].isBoolean()) { hasAdditionalProperties = value["additionalProperties"].asBoolean(); if (!hasAdditionalProperties) { additionalProperties.reset(); } } else if (value["additionalProperties"].isObject() && !value["additionalProperties"].isNull()) { if (!additionalProperties->Parse(value["additionalProperties"])) { missingReference = additionalProperties->missingReference; hasAdditionalProperties = false; additionalProperties.reset(); CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties schema definition in type {}", name); return false; } } else { CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties definition in type {}", name); return false; } } } // If the defined parameter is an array // we need to check for detailed definitions // of the array items if (HasType(type, ArrayValue)) { // Check for "uniqueItems" field if (value.isMember("uniqueItems") && value["uniqueItems"].isBoolean()) uniqueItems = value["uniqueItems"].asBoolean(); else 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()) { JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); if (additionalItem->Parse(value["additionalItems"])) additionalItems.push_back(additionalItem); else { CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value for type {}", name); missingReference = additionalItem->missingReference; return false; } } // 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++) { JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); if (additionalItem->Parse(value["additionalItems"][itemIndex])) additionalItems.push_back(additionalItem); else { CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value (item {}) for type {}", itemIndex, name); missingReference = additionalItem->missingReference; return false; } } } // 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"].isBoolean()) { CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" definition for type {}", name); return false; } } // If the "items" field is a single object // we can parse that directly if (value.isMember("items")) { if (value["items"].isObject()) { JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); if (!item->Parse(value["items"])) { CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" for type {}", name); missingReference = item->missingReference; return false; } 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 (CVariant::const_iterator_array itemItr = value["items"].begin_array(); itemItr != value["items"].end_array(); ++itemItr) { JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); if (!item->Parse(*itemItr)) { CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" array for type {}", name); missingReference = item->missingReference; return false; } items.push_back(item); } } } minItems = (unsigned int)value["minItems"].asUnsignedInteger(0); maxItems = (unsigned int)value["maxItems"].asUnsignedInteger(0); } if (HasType(type, NumberValue) || HasType(type, IntegerValue)) { if ((type & NumberValue) == NumberValue) { minimum = value["minimum"].asDouble(-std::numeric_limits::max()); maximum = value["maximum"].asDouble(std::numeric_limits::max()); } else if ((type & IntegerValue) == IntegerValue) { minimum = (double)value["minimum"].asInteger(std::numeric_limits::min()); maximum = (double)value["maximum"].asInteger(std::numeric_limits::max()); } exclusiveMinimum = value["exclusiveMinimum"].asBoolean(false); exclusiveMaximum = value["exclusiveMaximum"].asBoolean(false); divisibleBy = (unsigned int)value["divisibleBy"].asUnsignedInteger(0); } if (HasType(type, StringValue)) { minLength = (int)value["minLength"].asInteger(-1); maxLength = (int)value["maxLength"].asInteger(-1); } // 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 (CVariant::const_iterator_array enumItr = value["enum"].begin_array(); enumItr != value["enum"].end_array(); ++enumItr) { // Check for duplicates and eliminate them bool approved = true; for (unsigned int approvedIndex = 0; approvedIndex < enums.size(); approvedIndex++) { if (*enumItr == enums.at(approvedIndex)) { approved = false; break; } } // Only add the current item to the enum value // list if it is not duplicate if (approved) enums.push_back(*enumItr); } } if (type != ObjectValue) { // 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)) { if (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 (std::vector::const_iterator itr = enums.begin(); itr != enums.end(); ++itr) { if (value["default"] == *itr) { ok = true; break; } } } } if (ok) 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)) CLog::Log(LOGDEBUG, "JSONRPC: Parameter {} has an invalid default value", name); // If the type contains an "enum" we need to get the // default value from the first enum value if (enums.size() > 0) defaultValue = enums.at(0); // otherwise set a default value instead else SetDefaultValue(defaultValue, type); } } return true; } JSONRPC_STATUS JSONSchemaTypeDefinition::Check(const CVariant& value, CVariant& outputValue, CVariant& errorData) const { if (!name.empty()) errorData["name"] = name; SchemaValueTypeToJson(type, errorData["type"]); std::string errorMessage; // Let's check the type of the provided parameter if (!IsType(value, type)) { errorMessage = StringUtils::Format("Invalid type {} received", ValueTypeToString(value.type())); errorData["message"] = errorMessage.c_str(); return InvalidParams; } else if (value.isNull() && !HasType(type, NullValue)) { errorData["message"] = "Received value is null"; return InvalidParams; } // Let's check if we have to handle a union type if (unionTypes.size() > 0) { bool ok = false; for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++) { CVariant dummyError; CVariant testOutput = outputValue; if (unionTypes.at(unionIndex)->Check(value, testOutput, dummyError) == OK) { ok = true; outputValue = testOutput; break; } } if (!ok) { errorData["message"] = "Received value does not match any of the union type definitions"; return InvalidParams; } } // First we need to check if this type extends another // type and if so we need to check against the extended // type first if (extends.size() > 0) { for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++) { JSONRPC_STATUS status = extends.at(extendsIndex)->Check(value, outputValue, errorData); if (status != OK) { CLog::Log(LOGDEBUG, "JSONRPC: Value does not match extended type {} of type {}", extends.at(extendsIndex)->ID, name); errorMessage = StringUtils::Format("value does not match extended type {}", extends.at(extendsIndex)->ID); errorData["message"] = errorMessage.c_str(); return status; } } } // 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, ArrayValue) && value.isArray()) { outputValue = CVariant(CVariant::VariantTypeArray); // Check the number of items against minItems and maxItems if ((minItems > 0 && value.size() < minItems) || (maxItems > 0 && value.size() > maxItems)) { CLog::Log( LOGDEBUG, "JSONRPC: Number of array elements does not match minItems and/or maxItems in type {}", name); if (minItems > 0 && maxItems > 0) errorMessage = StringUtils::Format("Between {} and {} array items expected but {} received", minItems, maxItems, value.size()); else if (minItems > 0) errorMessage = StringUtils::Format("At least {} array items expected but only {} received", minItems, value.size()); else errorMessage = StringUtils::Format("Only {} array items expected but {} received", maxItems, value.size()); errorData["message"] = errorMessage.c_str(); return InvalidParams; } if (items.size() == 0) outputValue = value; else if (items.size() == 1) { JSONSchemaTypeDefinitionPtr itemType = items.at(0); // Loop through all array elements for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++) { CVariant temp; JSONRPC_STATUS status = itemType->Check(value[arrayIndex], temp, errorData["property"]); outputValue.push_back(temp); if (status != OK) { CLog::Log(LOGDEBUG, "JSONRPC: Array element at index {} does not match in type {}", arrayIndex, name); errorMessage = StringUtils::Format("array element at index {} does not match", arrayIndex); 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() < items.size() || (value.size() != items.size() && additionalItems.size() == 0)) { CLog::Log(LOGDEBUG, "JSONRPC: One of the array elements does not match in type {}", name); errorMessage = StringUtils::Format("{0} array elements expected but {1} received", 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 < std::min(items.size(), (size_t)value.size()); arrayIndex++) { JSONRPC_STATUS status = items.at(arrayIndex)->Check(value[arrayIndex], outputValue[arrayIndex], errorData["property"]); if (status != OK) { CLog::Log( LOGDEBUG, "JSONRPC: Array element at index {} does not match with items schema in type {}", arrayIndex, name); return status; } } if (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 < additionalItems.size(); additionalIndex++) { CVariant dummyError; if (additionalItems.at(additionalIndex)->Check(value[arrayIndex], outputValue[arrayIndex], dummyError) == OK) { ok = true; break; } } if (!ok) { CLog::Log(LOGDEBUG, "JSONRPC: Array contains non-conforming additional items in type {}", name); errorMessage = StringUtils::Format( "Array element at index {} 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 (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 (outputValue[checkingIndex] == outputValue[checkedIndex]) { CLog::Log(LOGDEBUG, "JSONRPC: Not unique array element at index {} and {} in type {}", checkingIndex, checkedIndex, name); errorMessage = StringUtils::Format( "Array element at index {} is not unique (same as array element at index {})", 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, ObjectValue) && value.isObject()) { unsigned int handled = 0; JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end(); JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator; for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator) { if (value.isMember(propertiesIterator->second->name)) { JSONRPC_STATUS status = propertiesIterator->second->Check(value[propertiesIterator->second->name], outputValue[propertiesIterator->second->name], errorData["property"]); if (status != OK) { CLog::Log(LOGDEBUG, "JSONRPC: Invalid property \"{}\" in type {}", propertiesIterator->second->name, name); return status; } handled++; } else if (propertiesIterator->second->optional) outputValue[propertiesIterator->second->name] = propertiesIterator->second->defaultValue; else { errorData["property"]["name"] = propertiesIterator->second->name.c_str(); errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second->type); errorData["message"] = "Missing property"; return InvalidParams; } } // Additional properties are not allowed if (handled < value.size()) { // If additional properties are allowed we need to check if // they match the defined schema if (hasAdditionalProperties && additionalProperties != NULL) { CVariant::const_iterator_map iter; CVariant::const_iterator_map iterEnd = value.end_map(); for (iter = value.begin_map(); iter != iterEnd; ++iter) { if (properties.find(iter->first) != properties.end()) continue; // If the additional property is of type "any" // we can simply copy its value to the output // object if (additionalProperties->type == AnyValue) { outputValue[iter->first] = value[iter->first]; continue; } JSONRPC_STATUS status = additionalProperties->Check(value[iter->first], outputValue[iter->first], errorData["property"]); if (status != OK) { CLog::Log(LOGDEBUG, "JSONRPC: Invalid additional property \"{}\" in type {}", iter->first, name); return status; } } } // If we still have unchecked properties but additional // properties are not allowed, we have invalid parameters else if (!hasAdditionalProperties || additionalProperties == NULL) { errorData["message"] = "Unexpected additional properties received"; errorData.erase("property"); 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 (enums.size() > 0) { bool valid = false; for (const auto& enumItr : enums) { if (enumItr == value) { valid = true; break; } } if (!valid) { CLog::Log(LOGDEBUG, "JSONRPC: Value does not match any of the enum values in 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, NumberValue) && value.isDouble()) || (HasType(type, IntegerValue) && value.isInteger())) { double numberValue; if (value.isDouble()) numberValue = value.asDouble(); else numberValue = (double)value.asInteger(); // Check minimum if ((exclusiveMinimum && numberValue <= minimum) || (!exclusiveMinimum && numberValue < minimum) || // Check maximum (exclusiveMaximum && numberValue >= maximum) || (!exclusiveMaximum && numberValue > maximum)) { CLog::Log(LOGDEBUG, "JSONRPC: Value does not lay between minimum and maximum in type {}", name); if (value.isDouble()) errorMessage = StringUtils::Format("Value between {:f} ({}) and {:f} ({}) expected but {:f} received", minimum, exclusiveMinimum ? "exclusive" : "inclusive", maximum, exclusiveMaximum ? "exclusive" : "inclusive", numberValue); else errorMessage = StringUtils::Format( "Value between {} ({}) and {} ({}) expected but {} received", (int)minimum, exclusiveMinimum ? "exclusive" : "inclusive", (int)maximum, exclusiveMaximum ? "exclusive" : "inclusive", (int)numberValue); errorData["message"] = errorMessage.c_str(); return InvalidParams; } // Check divisibleBy if ((HasType(type, IntegerValue) && divisibleBy > 0 && ((int)numberValue % divisibleBy) != 0)) { CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet divisibleBy requirements in type {}", name); errorMessage = StringUtils::Format("Value should be divisible by {} but {} received", divisibleBy, (int)numberValue); errorData["message"] = errorMessage.c_str(); return InvalidParams; } } // If we have a string, we need to check the length if (HasType(type, StringValue) && value.isString()) { int size = static_cast(value.asString().size()); if (size < minLength) { CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet minLength requirements in type {}", name); errorMessage = StringUtils::Format( "Value should have a minimum length of {} but has a length of {}", minLength, size); errorData["message"] = errorMessage.c_str(); return InvalidParams; } if (maxLength >= 0 && size > maxLength) { CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet maxLength requirements in type {}", name); errorMessage = StringUtils::Format( "Value should have a maximum length of {} but has a length of {}", maxLength, size); errorData["message"] = errorMessage.c_str(); return InvalidParams; } } // Otherwise it can have any value outputValue = value; return OK; } void JSONSchemaTypeDefinition::Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const { bool typeReference = false; // Printing general fields if (isParameter) output["name"] = name; if (isGlobal) output["id"] = ID; else if (!ID.empty()) { output["$ref"] = ID; typeReference = true; } if (printDescriptions && !description.empty()) output["description"] = description; if (isParameter || printDefault) { if (!optional) output["required"] = true; if (optional && type != ObjectValue && type != ArrayValue) output["default"] = defaultValue; } if (!typeReference) { if (extends.size() == 1) { output["extends"] = extends.at(0)->ID; } else if (extends.size() > 1) { output["extends"] = CVariant(CVariant::VariantTypeArray); for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++) output["extends"].append(extends.at(extendsIndex)->ID); } else if (unionTypes.size() > 0) { output["type"] = CVariant(CVariant::VariantTypeArray); for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++) { CVariant unionOutput = CVariant(CVariant::VariantTypeObject); unionTypes.at(unionIndex)->Print(false, false, false, printDescriptions, unionOutput); output["type"].append(unionOutput); } } else CJSONUtils::SchemaValueTypeToJson(type, output["type"]); // Printing enum field if (enums.size() > 0) { output["enums"] = CVariant(CVariant::VariantTypeArray); for (unsigned int enumIndex = 0; enumIndex < enums.size(); enumIndex++) output["enums"].append(enums.at(enumIndex)); } // Printing integer/number fields if (CJSONUtils::HasType(type, IntegerValue) || CJSONUtils::HasType(type, NumberValue)) { if (CJSONUtils::HasType(type, NumberValue)) { if (minimum > -std::numeric_limits::max()) output["minimum"] = minimum; if (maximum < std::numeric_limits::max()) output["maximum"] = maximum; } else { if (minimum > std::numeric_limits::min()) output["minimum"] = (int)minimum; if (maximum < std::numeric_limits::max()) output["maximum"] = (int)maximum; } if (exclusiveMinimum) output["exclusiveMinimum"] = true; if (exclusiveMaximum) output["exclusiveMaximum"] = true; if (divisibleBy > 0) output["divisibleBy"] = divisibleBy; } if (CJSONUtils::HasType(type, StringValue)) { if (minLength >= 0) output["minLength"] = minLength; if (maxLength >= 0) output["maxLength"] = maxLength; } // Print array fields if (CJSONUtils::HasType(type, ArrayValue)) { if (items.size() == 1) { items.at(0)->Print(false, false, false, printDescriptions, output["items"]); } else if (items.size() > 1) { output["items"] = CVariant(CVariant::VariantTypeArray); for (unsigned int itemIndex = 0; itemIndex < items.size(); itemIndex++) { CVariant item = CVariant(CVariant::VariantTypeObject); items.at(itemIndex)->Print(false, false, false, printDescriptions, item); output["items"].append(item); } } if (minItems > 0) output["minItems"] = minItems; if (maxItems > 0) output["maxItems"] = maxItems; if (additionalItems.size() == 1) { additionalItems.at(0)->Print(false, false, false, printDescriptions, output["additionalItems"]); } else if (additionalItems.size() > 1) { output["additionalItems"] = CVariant(CVariant::VariantTypeArray); for (unsigned int addItemIndex = 0; addItemIndex < additionalItems.size(); addItemIndex++) { CVariant item = CVariant(CVariant::VariantTypeObject); additionalItems.at(addItemIndex)->Print(false, false, false, printDescriptions, item); output["additionalItems"].append(item); } } if (uniqueItems) output["uniqueItems"] = true; } // Print object fields if (CJSONUtils::HasType(type, ObjectValue)) { if (properties.size() > 0) { output["properties"] = CVariant(CVariant::VariantTypeObject); JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end(); JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator; for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator) { propertiesIterator->second->Print(false, false, true, printDescriptions, output["properties"][propertiesIterator->first]); } } if (!hasAdditionalProperties) output["additionalProperties"] = false; else if (additionalProperties != NULL && additionalProperties->type != AnyValue) additionalProperties->Print(false, false, true, printDescriptions, output["additionalProperties"]); } } } void JSONSchemaTypeDefinition::ResolveReference() { // Check and set the reference type before recursing // to guard against cycles if (referencedTypeSet) return; referencedTypeSet = true; // Take care of all nested types for (const auto& it : extends) it->ResolveReference(); for (const auto& it : unionTypes) it->ResolveReference(); for (const auto& it : items) it->ResolveReference(); for (const auto& it : additionalItems) it->ResolveReference(); for (const auto& it : properties) it.second->ResolveReference(); if (additionalProperties) additionalProperties->ResolveReference(); if (referencedType == nullptr) return; std::string origName = name; std::string origDescription = description; bool origOptional = optional; CVariant origDefaultValue = defaultValue; JSONSchemaTypeDefinitionPtr referencedTypeDef = referencedType; // set all the values from the given type definition *this = *referencedType; // restore the original values if (!origName.empty()) name = origName; if (!origDescription.empty()) description = origDescription; if (!origOptional) optional = origOptional; if (!origDefaultValue.isNull()) defaultValue = origDefaultValue; if (referencedTypeDef.get() != NULL) referencedType = referencedTypeDef; // This will have been overwritten by the copy of the reference // type so we need to set it again referencedTypeSet = true; } JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap() : m_propertiesmap(std::map()) { } void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add( const JSONSchemaTypeDefinitionPtr& property) { std::string name = property->name; StringUtils::ToLower(name); m_propertiesmap[name] = property; } JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const { return m_propertiesmap.begin(); } JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const std::string& 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 static_cast(m_propertiesmap.size()); } JsonRpcMethod::JsonRpcMethod() : missingReference(), name(), method(NULL), description(), parameters(), returns(new JSONSchemaTypeDefinition()) { } bool JsonRpcMethod::Parse(const CVariant &value) { // Parse XBMC specific information about the method if (value.isMember("transport") && value["transport"].isArray()) { int transport = 0; for (unsigned int index = 0; index < value["transport"].size(); index++) transport |= StringToTransportLayer(value["transport"][index].asString()); transportneed = (TransportLayerCapability)transport; } else transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString() : ""); if (value.isMember("permission") && value["permission"].isArray()) { int permissions = 0; for (unsigned int index = 0; index < value["permission"].size(); index++) permissions |= StringToPermission(value["permission"][index].asString()); permission = (OperationPermission)permissions; } else permission = StringToPermission(value.isMember("permission") ? value["permission"].asString() : ""); description = GetString(value["description"], ""); // 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++) { CVariant 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("extends")) || (parameter.isMember("type") && !parameter["type"].isString() && !parameter["type"].isArray()) || (parameter.isMember("$ref") && !parameter["$ref"].isString()) || (parameter.isMember("extends") && !parameter["extends"].isString() && !parameter["extends"].isArray())) { CLog::Log(LOGDEBUG, "JSONRPC: Method {} has a badly defined parameter", name); return false; } // Parse the parameter and add it to the list // of defined parameters JSONSchemaTypeDefinitionPtr param = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); if (!parseParameter(parameter, param)) { missingReference = param->missingReference; return false; } parameters.push_back(param); } } // Parse the return value of the method if (!parseReturn(value)) { missingReference = returns->missingReference; return false; } return true; } JSONRPC_STATUS JsonRpcMethod::Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const { if (transport != NULL && (transport->GetCapabilities() & transportneed) == transportneed) { if (client != NULL && (client->GetPermissionFlags() & permission) == permission && (!notification || (permission & OPERATION_PERMISSION_NOTIFICATION) == permission)) { methodCall = method; // Count the number of actually handled (present) // parameters unsigned int handled = 0; CVariant errorData = CVariant(CVariant::VariantTypeObject); errorData["method"] = name; // Loop through all the parameters to check for (unsigned int i = 0; i < parameters.size(); i++) { // Evaluate the current parameter JSONRPC_STATUS status = checkParameter(requestParameters, 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; } return MethodNotFound; } bool JsonRpcMethod::parseParameter(const CVariant& value, const JSONSchemaTypeDefinitionPtr& parameter) { parameter->name = GetString(value["name"], ""); // Parse the type and default value of the parameter return parameter->Parse(value, true); } bool JsonRpcMethod::parseReturn(const CVariant &value) { // Only parse the "returns" definition if there is one if (!value.isMember("returns")) { returns->type = NullValue; return true; } // If the type of the return value is defined as a simple string we can parse it directly if (value["returns"].isString()) return CJSONServiceDescription::parseJSONSchemaType(value["returns"], returns->unionTypes, returns->type, missingReference); // otherwise we have to parse the whole type definition if (!returns->Parse(value["returns"])) { missingReference = returns->missingReference; return false; } return true; } JSONRPC_STATUS JsonRpcMethod::checkParameter(const CVariant& requestParameters, const JSONSchemaTypeDefinitionPtr& type, unsigned int position, CVariant& outputParameters, unsigned int& handled, CVariant& errorData) { // Let's check if the parameter has been provided if (ParameterExists(requestParameters, type->name, position)) { // Get the parameter CVariant parameterValue = GetParameter(requestParameters, type->name, position); // Evaluate the type of the parameter JSONRPC_STATUS status = type->Check(parameterValue, 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; } void CJSONServiceDescription::ResolveReferences() { for (const auto& it : m_types) it.second->ResolveReference(); } void CJSONServiceDescription::Cleanup() { // reset all of the static data m_notifications.clear(); m_actionMap.clear(); m_types.clear(); m_incompleteDefinitions.clear(); } bool CJSONServiceDescription::prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name) { if (description.empty()) { CLog::Log(LOGERROR, "JSONRPC: Missing JSON Schema definition for \"{}\"", name); return false; } if (description.at(0) != '{') description = StringUtils::Format("{{{:s}}}", description); // Make sure the method description actually exists and represents an object if (!CJSONVariantParser::Parse(description, descriptionObject) || !descriptionObject.isObject()) { CLog::Log(LOGERROR, "JSONRPC: Unable to parse JSON Schema definition for \"{}\"", name); return false; } CVariant::const_iterator_map member = descriptionObject.begin_map(); if (member != descriptionObject.end_map()) name = member->first; if (name.empty() || (!descriptionObject[name].isMember("type") && !descriptionObject[name].isMember("$ref") && !descriptionObject[name].isMember("extends"))) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for \"{}\"", name); return false; } return true; } bool CJSONServiceDescription::addMethod(const std::string &jsonMethod, MethodCall method) { CVariant descriptionObject; std::string methodName; std::string modJsonMethod = jsonMethod; // Make sure the method description actually exists and represents an object if (!prepareDescription(modJsonMethod, descriptionObject, methodName)) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for method \"{}\"", methodName); return false; } if (m_actionMap.find(methodName) != m_actionMap.end()) { CLog::Log(LOGERROR, "JSONRPC: There already is a method with the name \"{}\"", methodName); return false; } std::string type = GetString(descriptionObject[methodName]["type"], ""); if (type.compare("method") != 0) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for method \"{}\"", methodName); return false; } if (method == NULL) { unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap); for (unsigned int index = 0; index < size; index++) { if (methodName.compare(m_methodMaps[index].name) == 0) { method = m_methodMaps[index].method; break; } } if (method == NULL) { CLog::Log(LOGERROR, "JSONRPC: Missing implementation for method \"{}\"", methodName); return false; } } // Parse the details of the method JsonRpcMethod newMethod; newMethod.name = methodName; newMethod.method = method; if (!newMethod.Parse(descriptionObject[newMethod.name])) { CLog::Log(LOGERROR, "JSONRPC: Could not parse method \"{}\"", methodName); if (!newMethod.missingReference.empty()) { IncompleteSchemaDefinition incomplete; incomplete.Schema = modJsonMethod; incomplete.Type = SchemaDefinitionMethod; incomplete.Method = method; IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(newMethod.missingReference); if (iter == m_incompleteDefinitions.end()) m_incompleteDefinitions[newMethod.missingReference] = std::vector(); CLog::Log( LOGINFO, "JSONRPC: Adding method \"{}\" to list of incomplete definitions (waiting for \"{}\")", methodName, newMethod.missingReference); m_incompleteDefinitions[newMethod.missingReference].push_back(incomplete); } return false; } m_actionMap.add(newMethod); return true; } bool CJSONServiceDescription::AddType(const std::string &jsonType) { CVariant descriptionObject; std::string typeName; std::string modJsonType = jsonType; if (!prepareDescription(modJsonType, descriptionObject, typeName)) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for type \"{}\"", typeName); return false; } if (m_types.find(typeName) != m_types.end()) { CLog::Log(LOGERROR, "JSONRPC: There already is a type with the name \"{}\"", typeName); return false; } // Make sure the "id" attribute is correctly populated descriptionObject[typeName]["id"] = typeName; JSONSchemaTypeDefinitionPtr globalType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); globalType->name = typeName; globalType->ID = typeName; CJSONServiceDescription::addReferenceTypeDefinition(globalType); if (!globalType->Parse(descriptionObject[typeName])) { CLog::Log(LOGWARNING, "JSONRPC: Could not parse type \"{}\"", typeName); CJSONServiceDescription::removeReferenceTypeDefinition(typeName); if (!globalType->missingReference.empty()) { IncompleteSchemaDefinition incomplete; incomplete.Schema = modJsonType; incomplete.Type = SchemaDefinitionType; IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(globalType->missingReference); if (iter == m_incompleteDefinitions.end()) m_incompleteDefinitions[globalType->missingReference] = std::vector(); CLog::Log( LOGINFO, "JSONRPC: Adding type \"{}\" to list of incomplete definitions (waiting for \"{}\")", typeName, globalType->missingReference); m_incompleteDefinitions[globalType->missingReference].push_back(incomplete); } globalType.reset(); return false; } return true; } bool CJSONServiceDescription::AddMethod(const std::string &jsonMethod, MethodCall method) { if (method == NULL) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSONRPC method implementation"); return false; } return addMethod(jsonMethod, method); } bool CJSONServiceDescription::AddBuiltinMethod(const std::string &jsonMethod) { return addMethod(jsonMethod, NULL); } bool CJSONServiceDescription::AddNotification(const std::string &jsonNotification) { CVariant descriptionObject; std::string notificationName; std::string modJsonNotification = jsonNotification; // Make sure the notification description actually exists and represents an object if (!prepareDescription(modJsonNotification, descriptionObject, notificationName)) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for notification \"{}\"", notificationName); return false; } if (m_notifications.find(notificationName) != m_notifications.end()) { CLog::Log(LOGERROR, "JSONRPC: There already is a notification with the name \"{}\"", notificationName); return false; } std::string type = GetString(descriptionObject[notificationName]["type"], ""); if (type.compare("notification") != 0) { CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for notification \"{}\"", notificationName); return false; } m_notifications[notificationName] = descriptionObject; return true; } bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector &values, CVariant::VariantType type /* = CVariant::VariantTypeNull */, const CVariant &defaultValue /* = CVariant::ConstNullVariant */) { if (name.empty() || m_types.find(name) != m_types.end() || values.size() == 0) return false; JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); definition->ID = name; std::vector types; bool autoType = false; if (type == CVariant::VariantTypeNull) autoType = true; else types.push_back(type); for (unsigned int index = 0; index < values.size(); index++) { if (autoType) types.push_back(values[index].type()); else if (type != CVariant::VariantTypeConstNull && type != values[index].type()) return false; } definition->enums.insert(definition->enums.begin(), values.begin(), values.end()); int schemaType = (int)AnyValue; for (unsigned int index = 0; index < types.size(); index++) { JSONSchemaType currentType; switch (type) { case CVariant::VariantTypeString: currentType = StringValue; break; case CVariant::VariantTypeDouble: currentType = NumberValue; break; case CVariant::VariantTypeInteger: case CVariant::VariantTypeUnsignedInteger: currentType = IntegerValue; break; case CVariant::VariantTypeBoolean: currentType = BooleanValue; break; case CVariant::VariantTypeArray: currentType = ArrayValue; break; case CVariant::VariantTypeObject: currentType = ObjectValue; break; case CVariant::VariantTypeConstNull: currentType = AnyValue; break; default: case CVariant::VariantTypeNull: return false; } if (index == 0) schemaType = currentType; else schemaType |= (int)currentType; } definition->type = (JSONSchemaType)schemaType; if (defaultValue.type() == CVariant::VariantTypeConstNull) definition->defaultValue = definition->enums.at(0); else definition->defaultValue = defaultValue; addReferenceTypeDefinition(definition); return true; } bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector &values) { std::vector enums; enums.reserve(values.size()); for (const auto& it : values) enums.emplace_back(it); return AddEnum(name, enums, CVariant::VariantTypeString); } bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector &values) { std::vector enums; enums.reserve(values.size()); for (const auto& it : values) enums.emplace_back(it); return AddEnum(name, enums, CVariant::VariantTypeInteger); } const char* CJSONServiceDescription::GetVersion() { return JSONRPC_SERVICE_VERSION; } JSONRPC_STATUS CJSONServiceDescription::Print(CVariant &result, ITransportLayer *transport, IClient *client, bool printDescriptions /* = true */, bool printMetadata /* = false */, bool filterByTransport /* = true */, const std::string &filterByName /* = "" */, const std::string &filterByType /* = "" */, bool printReferences /* = true */) { std::map types; CJsonRpcMethodMap methods; std::map notifications; int clientPermissions = client->GetPermissionFlags(); int transportCapabilities = transport->GetCapabilities(); if (filterByName.size() > 0) { std::string name = filterByName; if (filterByType == "method") { StringUtils::ToLower(name); CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator = m_actionMap.find(name); if (methodIterator != m_actionMap.end() && (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport)) methods.add(methodIterator->second); else return InvalidParams; } else if (filterByType == "namespace") { // append a . delimiter to make sure we check for a namespace StringUtils::ToLower(name); name.append("."); CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end(); for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++) { // Check if the given name is at the very beginning of the method name if (methodIterator->first.find(name) == 0 && (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport)) methods.add(methodIterator->second); } if (methods.begin() == methods.end()) return InvalidParams; } else if (filterByType == "type") { std::map::const_iterator typeIterator = m_types.find(name); if (typeIterator != m_types.end()) types[typeIterator->first] = typeIterator->second; else return InvalidParams; } else if (filterByType == "notification") { std::map::const_iterator notificationIterator = m_notifications.find(name); if (notificationIterator != m_notifications.end()) notifications[notificationIterator->first] = notificationIterator->second; else return InvalidParams; } else return InvalidParams; // If we need to print all referenced types we have to go through all parameters etc if (printReferences) { std::vector referencedTypes; // Loop through all printed types to get all referenced types std::map::const_iterator typeIterator; std::map::const_iterator typeIteratorEnd = types.end(); for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator) getReferencedTypes(typeIterator->second, referencedTypes); // Loop through all printed method's parameters and return value to get all referenced types CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end(); for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++) { for (unsigned int index = 0; index < methodIterator->second.parameters.size(); index++) getReferencedTypes(methodIterator->second.parameters.at(index), referencedTypes); getReferencedTypes(methodIterator->second.returns, referencedTypes); } for (unsigned int index = 0; index < referencedTypes.size(); index++) { std::map::const_iterator typeIterator = m_types.find(referencedTypes.at(index)); if (typeIterator != m_types.end()) types[typeIterator->first] = typeIterator->second; } } } else { types = m_types; methods = m_actionMap; notifications = m_notifications; } // Print the header result["id"] = JSONRPC_SERVICE_ID; result["version"] = JSONRPC_SERVICE_VERSION; result["description"] = JSONRPC_SERVICE_DESCRIPTION; std::map::const_iterator typeIterator; std::map::const_iterator typeIteratorEnd = types.end(); for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator) { CVariant currentType = CVariant(CVariant::VariantTypeObject); typeIterator->second->Print(false, true, true, printDescriptions, currentType); result["types"][typeIterator->first] = currentType; } // Iterate through all json rpc methods CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end(); for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++) { if ((clientPermissions & methodIterator->second.permission) != methodIterator->second.permission || ((transportCapabilities & methodIterator->second.transportneed) != methodIterator->second.transportneed && filterByTransport)) continue; CVariant currentMethod = CVariant(CVariant::VariantTypeObject); currentMethod["type"] = "method"; if (printDescriptions && !methodIterator->second.description.empty()) currentMethod["description"] = methodIterator->second.description; if (printMetadata) { CVariant permissions(CVariant::VariantTypeArray); for (int i = ReadData; i <= OPERATION_PERMISSION_ALL; i *= 2) { if ((methodIterator->second.permission & i) == i) permissions.push_back(PermissionToString((OperationPermission)i)); } if (permissions.size() == 1) currentMethod["permission"] = permissions[0]; else currentMethod["permission"] = permissions; } currentMethod["params"] = CVariant(CVariant::VariantTypeArray); for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++) { CVariant param = CVariant(CVariant::VariantTypeObject); methodIterator->second.parameters.at(paramIndex)->Print(true, false, true, printDescriptions, param); currentMethod["params"].append(param); } methodIterator->second.returns->Print(false, false, false, printDescriptions, currentMethod["returns"]); result["methods"][methodIterator->second.name] = currentMethod; } // Print notification description std::map::const_iterator notificationIterator; std::map::const_iterator notificationIteratorEnd = notifications.end(); for (notificationIterator = notifications.begin(); notificationIterator != notificationIteratorEnd; ++notificationIterator) result["notifications"][notificationIterator->first] = notificationIterator->second[notificationIterator->first]; return OK; } JSONRPC_STATUS CJSONServiceDescription::CheckCall(const char* const method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) { CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method); if (iter != m_actionMap.end()) return iter->second.Check(requestParameters, transport, client, notification, methodCall, outputParameters); return MethodNotFound; } JSONSchemaTypeDefinitionPtr CJSONServiceDescription::GetType(const std::string &identification) { std::map::iterator iter = m_types.find(identification); if (iter == m_types.end()) return JSONSchemaTypeDefinitionPtr(); return iter->second; } bool CJSONServiceDescription::parseJSONSchemaType(const CVariant &value, std::vector& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference) { missingReference.clear(); schemaType = AnyValue; if (value.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.size(); typeIndex++) { JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); // If the type is a string try to parse it if (value[typeIndex].isString()) definition->type = StringToSchemaValueType(value[typeIndex].asString()); else if (value[typeIndex].isObject()) { if (!definition->Parse(value[typeIndex])) { missingReference = definition->missingReference; CLog::Log(LOGERROR, "JSONRPC: Invalid type schema in union type definition"); return false; } } else { CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition"); return false; } definition->optional = false; typeDefinitions.push_back(definition); parsedType |= definition->type; } // If the type has not been set yet set it to "any" if (parsedType != 0) schemaType = (JSONSchemaType)parsedType; return true; } if (value.isString()) { schemaType = StringToSchemaValueType(value.asString()); return true; } return false; } void CJSONServiceDescription::addReferenceTypeDefinition( const JSONSchemaTypeDefinitionPtr& 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.empty()) 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; IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(typeDefinition->ID); if (iter == m_incompleteDefinitions.end()) return; CLog::Log(LOGINFO, "JSONRPC: Resolving incomplete types/methods referencing {}", typeDefinition->ID); for (unsigned int index = 0; index < iter->second.size(); index++) { if (iter->second[index].Type == SchemaDefinitionType) AddType(iter->second[index].Schema); else AddMethod(iter->second[index].Schema, iter->second[index].Method); } m_incompleteDefinitions.erase(typeDefinition->ID); } void CJSONServiceDescription::removeReferenceTypeDefinition(const std::string &typeID) { if (typeID.empty()) return; std::map::iterator type = m_types.find(typeID); if (type != m_types.end()) m_types.erase(type); } void CJSONServiceDescription::getReferencedTypes(const JSONSchemaTypeDefinitionPtr& type, std::vector& referencedTypes) { // If the current type is a referenceable object, we can add it to the list if (type->ID.size() > 0) { for (unsigned int index = 0; index < referencedTypes.size(); index++) { // The referenceable object has already been added to the list so we can just skip it if (type->ID == referencedTypes.at(index)) return; } referencedTypes.push_back(type->ID); } // If the current type is an object we need to check its properties if (HasType(type->type, ObjectValue)) { JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iter; JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iterEnd = type->properties.end(); for (iter = type->properties.begin(); iter != iterEnd; ++iter) getReferencedTypes(iter->second, referencedTypes); } // If the current type is an array we need to check its items if (HasType(type->type, ArrayValue)) { unsigned int index; for (index = 0; index < type->items.size(); index++) getReferencedTypes(type->items.at(index), referencedTypes); for (index = 0; index < type->additionalItems.size(); index++) getReferencedTypes(type->additionalItems.at(index), referencedTypes); } // If the current type extends others type we need to check those types for (unsigned int index = 0; index < type->extends.size(); index++) getReferencedTypes(type->extends.at(index), referencedTypes); // If the current type is a union type we need to check those types for (unsigned int index = 0; index < type->unionTypes.size(); index++) getReferencedTypes(type->unionTypes.at(index), referencedTypes); } CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap(): m_actionmap(std::map()) { } void CJSONServiceDescription::CJsonRpcMethodMap::clear() { m_actionmap.clear(); } void CJSONServiceDescription::CJsonRpcMethodMap::add(const JsonRpcMethod &method) { std::string name = method.name; StringUtils::ToLower(name); m_actionmap[name] = method; } CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const { return m_actionmap.begin(); } CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const std::string& key) const { return m_actionmap.find(key); } CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const { return m_actionmap.end(); }