/* * Copyright (C) 2022 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 "ExecString.h" #include "FileItem.h" #include "ServiceBroker.h" #include "URL.h" #include "music/tags/MusicInfoTag.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" #include "utils/Variant.h" #include "utils/log.h" #include "video/VideoInfoTag.h" CExecString::CExecString(const std::string& execString) { m_valid = Parse(execString); } CExecString::CExecString(const std::string& function, const std::vector& params) : m_function(function), m_params(params) { m_valid = !m_function.empty(); if (m_valid) SetExecString(); } CExecString::CExecString(const CFileItem& item, const std::string& contextWindow) { m_valid = Parse(item, contextWindow); } namespace { void SplitParams(const std::string& paramString, std::vector& parameters) { bool inQuotes = false; bool lastEscaped = false; // only every second character can be escaped int inFunction = 0; size_t whiteSpacePos = 0; std::string parameter; parameters.clear(); for (size_t pos = 0; pos < paramString.size(); pos++) { char ch = paramString[pos]; bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped); lastEscaped = escaped; if (inQuotes) { // if we're in a quote, we accept everything until the closing quote if (ch == '"' && !escaped) { // finished a quote - no need to add the end quote to our string inQuotes = false; } } else { // not in a quote, so check if we should be starting one if (ch == '"' && !escaped) { // start of quote - no need to add the quote to our string inQuotes = true; } if (inFunction && ch == ')') { // end of a function inFunction--; } if (ch == '(') { // start of function inFunction++; } if (!inFunction && ch == ',') { // not in a function, so a comma signifies the end of this parameter if (whiteSpacePos) parameter = parameter.substr(0, whiteSpacePos); // trim off start and end quotes if (parameter.length() > 1 && parameter[0] == '"' && parameter[parameter.length() - 1] == '"') parameter = parameter.substr(1, parameter.length() - 2); else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"') { // check name="value" style param. size_t quotaPos = parameter.find('"'); if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=') { parameter.erase(parameter.length() - 1); parameter.erase(quotaPos); } } parameters.push_back(parameter); parameter.clear(); whiteSpacePos = 0; continue; } } if ((ch == '"' || ch == '\\') && escaped) { // escaped quote or backslash parameter[parameter.size() - 1] = ch; continue; } // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter if (ch == ' ' && !inQuotes) { if (parameter.empty()) // skip whitespace on left continue; if (!whiteSpacePos) // make a note of where whitespace starts on the right whiteSpacePos = parameter.size(); } else whiteSpacePos = 0; parameter += ch; } if (inFunction || inQuotes) CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__, paramString); if (whiteSpacePos) parameter.erase(whiteSpacePos); // trim off start and end quotes if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"') parameter = parameter.substr(1, parameter.size() - 2); else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"') { // check name="value" style param. size_t quotaPos = parameter.find('"'); if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=') { parameter.erase(parameter.length() - 1); parameter.erase(quotaPos); } } if (!parameter.empty() || parameters.size()) parameters.push_back(parameter); } void SplitExecFunction(const std::string& execString, std::string& function, std::vector& parameters) { std::string paramString; size_t iPos = execString.find('('); size_t iPos2 = execString.rfind(')'); if (iPos != std::string::npos && iPos2 != std::string::npos) { paramString = execString.substr(iPos + 1, iPos2 - iPos - 1); function = execString.substr(0, iPos); } else function = execString; // remove any whitespace, and the standard prefix (if it exists) StringUtils::Trim(function); SplitParams(paramString, parameters); } } // namespace bool CExecString::Parse(const std::string& execString) { m_execString = execString; SplitExecFunction(m_execString, m_function, m_params); // Keep original function case in execstring, lowercase it in function StringUtils::ToLower(m_function); return true; } bool CExecString::Parse(const CFileItem& item, const std::string& contextWindow) { if (item.IsFavourite()) { const CURL url(item.GetPath()); Parse(CURL::Decode(url.GetHostName())); } else if (item.m_bIsFolder && (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders || !(item.IsSmartPlayList() || item.IsPlayList()))) { if (!contextWindow.empty()) Build("ActivateWindow", {contextWindow, StringUtils::Paramify(item.GetPath()), "return"}); } else if (item.IsScript() && item.GetPath().size() > 9) // script:// Build("RunScript", {StringUtils::Paramify(item.GetPath().substr(9))}); else if (item.IsAddonsPath() && item.GetPath().size() > 9) // addons:// { const CURL url(item.GetPath()); if (url.GetHostName() == "install") Build("InstallFromZip", {}); else if (url.GetHostName() == "check_for_updates") Build("UpdateAddonRepos", {"showProgress"}); else Build("RunAddon", {StringUtils::Paramify(url.GetFileName())}); } else if (item.IsAndroidApp() && item.GetPath().size() > 26) // androidapp://sources/apps/ Build("StartAndroidActivity", {StringUtils::Paramify(item.GetPath().substr(26))}); else // assume a media file { if (item.IsVideoDb() && item.HasVideoInfoTag()) BuildPlayMedia(item, StringUtils::Paramify(item.GetVideoInfoTag()->m_strFileNameAndPath)); else if (item.IsMusicDb() && item.HasMusicInfoTag()) BuildPlayMedia(item, StringUtils::Paramify(item.GetMusicInfoTag()->GetURL())); else if (item.IsPicture()) Build("ShowPicture", {StringUtils::Paramify(item.GetPath())}); else { // Everything else will be treated as PlayMedia for item's path BuildPlayMedia(item, StringUtils::Paramify(item.GetPath())); } } return true; } void CExecString::Build(const std::string& function, const std::vector& params) { m_function = function; m_params = params; SetExecString(); } void CExecString::BuildPlayMedia(const CFileItem& item, const std::string& target) { std::vector params{target}; if (item.HasProperty("playlist_type_hint")) params.emplace_back("playlist_type_hint=" + item.GetProperty("playlist_type_hint").asString()); Build("PlayMedia", params); } void CExecString::SetExecString() { if (m_params.empty()) m_execString = m_function; else m_execString = StringUtils::Format("{}({})", m_function, StringUtils::Join(m_params, ",")); // Keep original function case in execstring, lowercase it in function StringUtils::ToLower(m_function); }