2 * Copyright (C) 2022 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "ExecString.h"
12 #include "ServiceBroker.h"
14 #include "music/tags/MusicInfoTag.h"
15 #include "settings/AdvancedSettings.h"
16 #include "settings/SettingsComponent.h"
17 #include "utils/StringUtils.h"
18 #include "utils/Variant.h"
19 #include "utils/log.h"
20 #include "video/VideoInfoTag.h"
22 CExecString::CExecString(const std::string
& execString
)
24 m_valid
= Parse(execString
);
27 CExecString::CExecString(const std::string
& function
, const std::vector
<std::string
>& params
)
28 : m_function(function
), m_params(params
)
30 m_valid
= !m_function
.empty();
36 CExecString::CExecString(const CFileItem
& item
, const std::string
& contextWindow
)
38 m_valid
= Parse(item
, contextWindow
);
43 void SplitParams(const std::string
& paramString
, std::vector
<std::string
>& parameters
)
45 bool inQuotes
= false;
46 bool lastEscaped
= false; // only every second character can be escaped
48 size_t whiteSpacePos
= 0;
49 std::string parameter
;
51 for (size_t pos
= 0; pos
< paramString
.size(); pos
++)
53 char ch
= paramString
[pos
];
54 bool escaped
= (pos
> 0 && paramString
[pos
- 1] == '\\' && !lastEscaped
);
55 lastEscaped
= escaped
;
57 { // if we're in a quote, we accept everything until the closing quote
58 if (ch
== '"' && !escaped
)
59 { // finished a quote - no need to add the end quote to our string
64 { // not in a quote, so check if we should be starting one
65 if (ch
== '"' && !escaped
)
66 { // start of quote - no need to add the quote to our string
69 if (inFunction
&& ch
== ')')
70 { // end of a function
74 { // start of function
77 if (!inFunction
&& ch
== ',')
78 { // not in a function, so a comma signifies the end of this parameter
80 parameter
= parameter
.substr(0, whiteSpacePos
);
81 // trim off start and end quotes
82 if (parameter
.length() > 1 && parameter
[0] == '"' &&
83 parameter
[parameter
.length() - 1] == '"')
84 parameter
= parameter
.substr(1, parameter
.length() - 2);
85 else if (parameter
.length() > 3 && parameter
[parameter
.length() - 1] == '"')
87 // check name="value" style param.
88 size_t quotaPos
= parameter
.find('"');
89 if (quotaPos
> 1 && quotaPos
< parameter
.length() - 1 && parameter
[quotaPos
- 1] == '=')
91 parameter
.erase(parameter
.length() - 1);
92 parameter
.erase(quotaPos
);
95 parameters
.push_back(parameter
);
101 if ((ch
== '"' || ch
== '\\') && escaped
)
102 { // escaped quote or backslash
103 parameter
[parameter
.size() - 1] = ch
;
106 // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter
107 if (ch
== ' ' && !inQuotes
)
109 if (parameter
.empty()) // skip whitespace on left
111 if (!whiteSpacePos
) // make a note of where whitespace starts on the right
112 whiteSpacePos
= parameter
.size();
118 if (inFunction
|| inQuotes
)
119 CLog::Log(LOGWARNING
, "{}({}) - end of string while searching for ) or \"", __FUNCTION__
,
122 parameter
.erase(whiteSpacePos
);
123 // trim off start and end quotes
124 if (parameter
.size() > 1 && parameter
[0] == '"' && parameter
[parameter
.size() - 1] == '"')
125 parameter
= parameter
.substr(1, parameter
.size() - 2);
126 else if (parameter
.size() > 3 && parameter
[parameter
.size() - 1] == '"')
128 // check name="value" style param.
129 size_t quotaPos
= parameter
.find('"');
130 if (quotaPos
> 1 && quotaPos
< parameter
.length() - 1 && parameter
[quotaPos
- 1] == '=')
132 parameter
.erase(parameter
.length() - 1);
133 parameter
.erase(quotaPos
);
136 if (!parameter
.empty() || parameters
.size())
137 parameters
.push_back(parameter
);
140 void SplitExecFunction(const std::string
& execString
,
141 std::string
& function
,
142 std::vector
<std::string
>& parameters
)
144 std::string paramString
;
146 size_t iPos
= execString
.find('(');
147 size_t iPos2
= execString
.rfind(')');
148 if (iPos
!= std::string::npos
&& iPos2
!= std::string::npos
)
150 paramString
= execString
.substr(iPos
+ 1, iPos2
- iPos
- 1);
151 function
= execString
.substr(0, iPos
);
154 function
= execString
;
156 // remove any whitespace, and the standard prefix (if it exists)
157 StringUtils::Trim(function
);
159 SplitParams(paramString
, parameters
);
163 bool CExecString::Parse(const std::string
& execString
)
165 m_execString
= execString
;
166 SplitExecFunction(m_execString
, m_function
, m_params
);
168 // Keep original function case in execstring, lowercase it in function
169 StringUtils::ToLower(m_function
);
173 bool CExecString::Parse(const CFileItem
& item
, const std::string
& contextWindow
)
175 if (item
.IsFavourite())
177 const CURL
url(item
.GetPath());
178 Parse(CURL::Decode(url
.GetHostName()));
180 else if (item
.m_bIsFolder
&&
181 (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders
||
182 !(item
.IsSmartPlayList() || item
.IsPlayList())))
184 if (!contextWindow
.empty())
185 Build("ActivateWindow", {contextWindow
, StringUtils::Paramify(item
.GetPath()), "return"});
187 else if (item
.IsScript() && item
.GetPath().size() > 9) // script://<foo>
188 Build("RunScript", {StringUtils::Paramify(item
.GetPath().substr(9))});
189 else if (item
.IsAddonsPath() && item
.GetPath().size() > 9) // addons://<foo>
191 const CURL
url(item
.GetPath());
192 if (url
.GetHostName() == "install")
193 Build("InstallFromZip", {});
194 else if (url
.GetHostName() == "check_for_updates")
195 Build("UpdateAddonRepos", {"showProgress"});
197 Build("RunAddon", {StringUtils::Paramify(url
.GetFileName())});
199 else if (item
.IsAndroidApp() && item
.GetPath().size() > 26) // androidapp://sources/apps/<foo>
200 Build("StartAndroidActivity", {StringUtils::Paramify(item
.GetPath().substr(26))});
201 else // assume a media file
203 if (item
.IsVideoDb() && item
.HasVideoInfoTag())
204 BuildPlayMedia(item
, StringUtils::Paramify(item
.GetVideoInfoTag()->m_strFileNameAndPath
));
205 else if (item
.IsMusicDb() && item
.HasMusicInfoTag())
206 BuildPlayMedia(item
, StringUtils::Paramify(item
.GetMusicInfoTag()->GetURL()));
207 else if (item
.IsPicture())
208 Build("ShowPicture", {StringUtils::Paramify(item
.GetPath())});
211 // Everything else will be treated as PlayMedia for item's path
212 BuildPlayMedia(item
, StringUtils::Paramify(item
.GetPath()));
218 void CExecString::Build(const std::string
& function
, const std::vector
<std::string
>& params
)
220 m_function
= function
;
225 void CExecString::BuildPlayMedia(const CFileItem
& item
, const std::string
& target
)
227 std::vector
<std::string
> params
{target
};
229 if (item
.HasProperty("playlist_type_hint"))
230 params
.emplace_back("playlist_type_hint=" + item
.GetProperty("playlist_type_hint").asString());
232 Build("PlayMedia", params
);
235 void CExecString::SetExecString()
237 if (m_params
.empty())
238 m_execString
= m_function
;
240 m_execString
= StringUtils::Format("{}({})", m_function
, StringUtils::Join(m_params
, ","));
242 // Keep original function case in execstring, lowercase it in function
243 StringUtils::ToLower(m_function
);