已开启
增加execCmd执行cli tool #20318
增加execCmd执行cli tool #20318
已开启
piki创建于 4 天前
8 个文件变更+498-4
@@ -291,6 +291,25 @@ bool UnwrapExecCmdOptions(napi_env env, napi_value obj, ExecCmdParam &param,
291 }291 }
292 }292 }
293 293 
294+ if (napi_has_named_property(env, obj, "isShellCommand", &hasProperty) != napi_ok) {
295+ msg = "has isShellCommand failed";
296+ TAG_LOGE(AAFwkTag::CLI_TOOL, "%{public}s", msg.c_str());
297+ return false;
298+ }
299+ if (hasProperty) {
300+ napi_value isShellProp = nullptr;
301+ if (napi_get_named_property(env, obj, "isShellCommand", &isShellProp) != napi_ok) {
302+ msg = "invalid isShellCommand property";
303+ TAG_LOGE(AAFwkTag::CLI_TOOL, "%{public}s", msg.c_str());
304+ return false;
305+ }
306+ if (!AppExecFwk::UnwrapBoolFromJS2(env, isShellProp, param.isShellCommand)) {
307+ msg = "unwrap isShellCommand failed";
308+ TAG_LOGE(AAFwkTag::CLI_TOOL, "%{public}s", msg.c_str());
309+ return false;
310+ }
311+ }
312+ 
294 if (napi_has_named_property(env, obj, "callback", &hasProperty) != napi_ok) {313 if (napi_has_named_property(env, obj, "callback", &hasProperty) != napi_ok) {
295 msg = "has callback failed";314 msg = "has callback failed";
296 TAG_LOGE(AAFwkTag::CLI_TOOL, "%{public}s", msg.c_str());315 TAG_LOGE(AAFwkTag::CLI_TOOL, "%{public}s", msg.c_str());
@@ -33,6 +33,7 @@ public:
33 std::string env;33 std::string env;
34 std::string policy;34 std::string policy;
35 ExecOptions options;35 ExecOptions options;
36+ bool isShellCommand = true;
36 37 
37 bool Marshalling(Parcel &parcel) const;38 bool Marshalling(Parcel &parcel) const;
38 static ExecCmdParam *Unmarshalling(Parcel &parcel);39 static ExecCmdParam *Unmarshalling(Parcel &parcel);
@@ -31,6 +31,16 @@ namespace CliTool {
31 31 
32namespace {32namespace {
33constexpr int32_t ERR_OK = 0;33constexpr int32_t ERR_OK = 0;
34+ 
35+std::string ExtractToolName(const std::string &cmd)
36+{
H
Hhwliujinwei1 天前

[次要] ExtractToolName 的逻辑与 cli_tool_manager_service.cpp 中 ExecCmdToolMode 内的 toolName 提取逻辑完全重复,建议将其抽取到 ToolUtil 中作为公共静态方法复用,避免逻辑不一致风险。

likedislike
37+ auto first = cmd.find_first_not_of(" \t");
38+ if (first == std::string::npos) {
39+ return "";
40+ }
41+ auto pos = cmd.find_first_of(" \t", first);
42+ return (pos == std::string::npos) ? cmd.substr(first) : cmd.substr(first, pos - first);
43+}
34} // namespace44} // namespace
35 45 
36CliToolMGRClient& CliToolMGRClient::GetInstance()46CliToolMGRClient& CliToolMGRClient::GetInstance()
@@ -85,7 +95,8 @@ ErrCode CliToolMGRClient::ExecCmd(const ExecCmdParam &param,
85 return GET_CLI_TOOL_MGR_SERVICE_FAILED;95 return GET_CLI_TOOL_MGR_SERVICE_FAILED;
86 }96 }
87 97 
88- std::string eventId = CliEventReplyManager::GetInstance().AddEventReplyCallback("shell",98+ std::string eventKey = param.isShellCommand ? "shell" : ExtractToolName(param.cmd);
H
Hhwliujinwei1 天前

[次要] 当 param.isShellCommand 为 false 且 param.cmd 为空或纯空白时,ExtractToolName 返回空字符串作为 eventKey,可能导致注册无法匹配的回调。建议在客户端侧也增加 cmd 非空校验,提前返回错误,避免无效回调注册。

likedislike
99+ std::string eventId = CliEventReplyManager::GetInstance().AddEventReplyCallback(eventKey,
89 [cb = std::move(callback)](const CliEventReplyResult &result) {100 [cb = std::move(callback)](const CliEventReplyResult &result) {
90 if (cb) {101 if (cb) {
91 CliSessionInfo sessionInfo;102 CliSessionInfo sessionInfo;
@@ -96,7 +107,7 @@ ErrCode CliToolMGRClient::ExecCmd(const ExecCmdParam &param,
96 }107 }
97 });108 });
98 109
99- std::string subscriptionId = CliSessionSubscriptionManager::GetInstance().AddProvisionalSubscription("shell",110+ std::string subscriptionId = CliSessionSubscriptionManager::GetInstance().AddProvisionalSubscription(eventKey,
100 [sessionEventCallback](const std::string &sessionId,111 [sessionEventCallback](const std::string &sessionId,
101 const std::string &subscriptionId, const CliToolEvent &event) {112 const std::string &subscriptionId, const CliToolEvent &event) {
102 if (sessionEventCallback) {113 if (sessionEventCallback) {
@@ -41,6 +41,10 @@ bool ExecCmdParam::Marshalling(Parcel &parcel) const
41 TAG_LOGE(AAFwkTag::CLI_TOOL, "Write options failed.");41 TAG_LOGE(AAFwkTag::CLI_TOOL, "Write options failed.");
42 return false;42 return false;
43 }43 }
44+ if (!parcel.WriteBool(isShellCommand)) {
45+ TAG_LOGE(AAFwkTag::CLI_TOOL, "Write isShellCommand failed.");
46+ return false;
47+ }
44 return true;48 return true;
45}49}
46 50 
@@ -74,6 +78,13 @@ ExecCmdParam *ExecCmdParam::Unmarshalling(Parcel &parcel)
74 return nullptr;78 return nullptr;
75 }79 }
76 result->options = *execOptions;80 result->options = *execOptions;
81+ 
82+ // Tail field for backward compat: ReadBool fails on old clients, defaults to true.
83+ result->isShellCommand = true;
84+ if (parcel.ReadBool(result->isShellCommand)) {
H
Hhwliujinwei1 天前

[提示] Unmarshalling 中 ReadBool 成功后直接 return result,若未来在 isShellCommand 之后新增字段,此早返回会导致后续字段无法读取。建议移除 if 内的 return,统一在函数末尾返回,保持与 Marshalling 字段顺序的一致性。

likedislike
85+ return result;
86+ }
87+ TAG_LOGD(AAFwkTag::CLI_TOOL, "isShellCommand not present in parcel, using default(true).");
77 return result;88 return result;
78}89}
79} // namespace CliTool90} // namespace CliTool
@@ -220,6 +220,14 @@ private:
220 220 
221 int32_t ValidateAndPrepareCmd(const ExecCmdParam &param, uint32_t tokenId,221 int32_t ValidateAndPrepareCmd(const ExecCmdParam &param, uint32_t tokenId,
222 std::string &sandboxConfig, std::string &bundleName);222 std::string &sandboxConfig, std::string &bundleName);
223+ int32_t ExecCmdToolMode(const ExecCmdParam &param, const std::string &eventId,
224+ const sptr<ICliToolManagerScheduler> &scheduler, const std::string &subscriptionId,
225+ int32_t callerPid, int32_t callerUid, uint32_t tokenId, const std::string &bundleName);
226+ int32_t SetupCmdSession(const ExecToolParam &toolParam, const ToolInfo &toolInfo,
227+ const std::string &sandboxConfig, const std::string &eventId,
228+ const std::string &subscriptionId, const sptr<ICliToolManagerScheduler> &scheduler,
229+ int32_t callerPid, int32_t callerUid, const std::string &bundleName,
230+ const std::string &toolName);
223 int32_t SetupAndStartSession(const ExecToolParam &param, const std::string &eventId,231 int32_t SetupAndStartSession(const ExecToolParam &param, const std::string &eventId,
224 const ToolInfo &toolInfo, const std::string &sandboxConfig, const std::string &bundleName);232 const ToolInfo &toolInfo, const std::string &sandboxConfig, const std::string &bundleName);
225 233 
@@ -62,6 +62,12 @@ public:
62 62 
63 static void TransferToCmdParam(const AAFwk::WantParams &args, std::vector<std::string> &execArgs);63 static void TransferToCmdParam(const AAFwk::WantParams &args, std::vector<std::string> &execArgs);
64 64 
65+ // Parse a concatenated tool command string (e.g. "ohos-aa start --bundlename com.x")
66+ // into an ExecToolParam. The parameter mapping and type recovery are driven by the
67+ // tool's inputSchema (subcommand schema takes precedence when subcommand is present).
68+ static int32_t ParseToolCommand(const std::string &cmd, const ToolInfo &toolInfo,
69+ ExecToolParam &param, std::string &detail);
70+ 
65 static bool IsSkillTool(const std::string &toolName);71 static bool IsSkillTool(const std::string &toolName);
66 static void NormalizeSkillParamKeys(AAFwk::WantParams &args);72 static void NormalizeSkillParamKeys(AAFwk::WantParams &args);
67 static void ExpandArgsJsonString(AAFwk::WantParams &args);73 static void ExpandArgsJsonString(AAFwk::WantParams &args);
@@ -1058,8 +1058,20 @@ int32_t CliToolManagerService::ExecCmd(const ExecCmdParam &param, const std::str
1058 return ret;1058 return ret;
1059 }1059 }
1060 auto tokenId = IPCSkeleton::GetCallingTokenID();1060 auto tokenId = IPCSkeleton::GetCallingTokenID();
1061- std::string sandboxConfig;
1062 std::string bundleName;1061 std::string bundleName;
1062+ AppExecFwk::BundleInfo bundleInfo;
1063+ if (ToolUtil::GetBundleInfoByTokenId(tokenId, bundleInfo)) {
1064+ bundleName = bundleInfo.name;
1065+ }
1066+ 
1067+ // Tool command mode
1068+ if (!param.isShellCommand) {
1069+ return ExecCmdToolMode(param, eventId, scheduler, subscriptionId,
1070+ callerPid, callerUid, tokenId, bundleName);
1071+ }
1072+ 
1073+ // Shell path (original logic)
1074+ std::string sandboxConfig;
1063 if (auto ret = ValidateAndPrepareCmd(param, tokenId, sandboxConfig, bundleName); ret != ERR_OK) {1075 if (auto ret = ValidateAndPrepareCmd(param, tokenId, sandboxConfig, bundleName); ret != ERR_OK) {
1064 return ret;1076 return ret;
1065 }1077 }
@@ -1101,6 +1113,109 @@ int32_t CliToolManagerService::ExecCmd(const ExecCmdParam &param, const std::str
1101 return ERR_OK;1113 return ERR_OK;
1102}1114}
1103 1115 
1116+int32_t CliToolManagerService::ExecCmdToolMode(const ExecCmdParam &param, const std::string &eventId,
1117+ const sptr<ICliToolManagerScheduler> &scheduler, const std::string &subscriptionId,
1118+ int32_t callerPid, int32_t callerUid, uint32_t tokenId, const std::string &bundleName)
1119+{
1120+ TAG_LOGI(AAFwkTag::CLI_TOOL,
1121+ "ExecCmdToolMode: cmd=%{public}s, callerPid=%{public}d, callerUid=%{public}d",
1122+ param.cmd.c_str(), callerPid, callerUid);
H
Hhwliujinwei1 天前

[重要] ExecCmdToolMode 中使用 %{public}s 打印完整的 param.cmd,命令字符串可能包含文件路径等敏感信息,违反安全编码规范'禁止打印文件路径等敏感信息'。建议仅打印 toolName 或使用 %{private}s 脱敏。

likedislike
1123+ 
1124+ // Step 1: Extract toolName, look up tool.
1125+ auto first = param.cmd.find_first_not_of(" \t");
1126+ if (first == std::string::npos) {
1127+ ReportCliExecuteFailed(bundleName, "", "invalid_tool_name");
1128+ return ERR_INVALID_PARAM;
1129+ }
1130+ auto pos = param.cmd.find_first_of(" \t", first);
1131+ std::string toolName = (pos == std::string::npos) ? param.cmd.substr(first) : param.cmd.substr(first, pos - first);
1132+ if (toolName.empty() || toolName[0] == '/') {
1133+ ReportCliExecuteFailed(bundleName, toolName, "invalid_tool_name");
H
Hhwliujinwei1 天前

[重要] toolName 校验仅检查 empty 和首字符 '/',未对 '..'、'./'、路径分隔符等特殊字符进行校验。虽然 toolName 主要用于 GetToolByName 查找,但根据安全编码规范'外部传入的路径要做规范化校验,对路径中的.、..、../等特殊字符严格校验',建议增加对 toolName 的白名单字符校验或拒绝包含路径分隔符的输入。

likedislike
1134+ return ERR_INVALID_PARAM;
1135+ }
1136+ 
1137+ ToolInfo toolInfo;
1138+ if (CliToolDataManager::GetInstance().GetToolByName(toolName, toolInfo) != ERR_OK) {
1139+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(ERR_TOOL_NOT_EXIST));
1140+ return ERR_TOOL_NOT_EXIST;
1141+ }
1142+ 
1143+ // Step 2: Parse command string.
1144+ ExecToolParam toolParam;
1145+ std::string detail;
1146+ auto parseRet = ToolUtil::ParseToolCommand(param.cmd, toolInfo, toolParam, detail);
1147+ if (parseRet != ERR_OK) {
1148+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(parseRet), detail);
1149+ return parseRet;
1150+ }
1151+ 
1152+ toolParam.options = param.options;
1153+ toolParam.challenge = "";
1154+ 
1155+ // Step 3: Validate and prepare sandbox. GenerateSandboxConfig may overwrite bundleName.
1156+ std::string sandboxConfig;
1157+ std::string mutableBundleName = bundleName;
1158+ if (auto ret = ValidateAndPrepareTool(toolParam, tokenId, toolInfo, sandboxConfig, mutableBundleName, detail);
1159+ ret != ERR_OK) {
1160+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(ret), detail);
1161+ return ret;
1162+ }
1163+ 
1164+ // Step 4: Subscribe before process creation (aligned with shell path).
1165+ return SetupCmdSession(toolParam, toolInfo, sandboxConfig, eventId, subscriptionId,
1166+ scheduler, callerPid, callerUid, mutableBundleName, toolName);
1167+}
1168+ 
1169+int32_t CliToolManagerService::SetupCmdSession(const ExecToolParam &toolParam, const ToolInfo &toolInfo,
1170+ const std::string &sandboxConfig, const std::string &eventId,
1171+ const std::string &subscriptionId, const sptr<ICliToolManagerScheduler> &scheduler,
1172+ int32_t callerPid, int32_t callerUid, const std::string &bundleName,
1173+ const std::string &toolName)
1174+{
1175+ auto record = CreateSessionRecord(toolParam, eventId);
1176+ if (record == nullptr) {
1177+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(ERR_NO_INIT));
1178+ return ERR_NO_INIT;
1179+ }
1180+ AddSessionRecord(record);
1181+ 
1182+ auto subscribeRet = SubscribeSession(record->sessionId, subscriptionId, scheduler);
1183+ if (subscribeRet != ERR_OK) {
1184+ RemoveSessionRecord(record->sessionId);
1185+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(subscribeRet));
1186+ return subscribeRet;
1187+ }
1188+ 
1189+ auto createRet = ProcessManager::GetInstance().CreateChildProcess(
1190+ toolParam, sandboxConfig, toolInfo, record);
1191+ if (createRet != ERR_OK) {
1192+ EventDispatcher::GetInstance().UnregisterSubscriber(
1193+ record->sessionId, subscriptionId, callerPid, callerUid);
1194+ RemoveSessionRecord(record->sessionId);
1195+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(createRet));
1196+ return createRet;
1197+ }
1198+ 
1199+ if (!RegisterSessionWithMonitors(record, toolParam.options)) {
1200+ ProcessManager::GetInstance().Killpg(record->processId);
1201+ EventDispatcher::GetInstance().UnregisterSubscriber(
1202+ record->sessionId, subscriptionId, callerPid, callerUid);
1203+ RemoveSessionRecord(record->sessionId);
1204+ ReportCliExecuteFailed(bundleName, toolName, GetFailureReason(ERR_NO_INIT));
1205+ return ERR_NO_INIT;
1206+ }
1207+ 
1208+ if (!bundleName.empty()) {
1209+ RegisterAppStateObserver(bundleName, record->callerPid);
1210+ }
1211+ 
1212+ if (toolParam.options.background) {
1213+ HandleBackgroundSessionReply(record, eventId);
1214+ }
1215+ 
1216+ return ERR_OK;
1217+}
1218+ 
1104void CliToolManagerService::PostExecToolTask(int64_t time, const std::string &sessionId, bool isTimeout)1219void CliToolManagerService::PostExecToolTask(int64_t time, const std::string &sessionId, bool isTimeout)
1105{1220{
1106 auto timeoutTask = [sessionId, isTimeout]() {1221 auto timeoutTask = [sessionId, isTimeout]() {
@@ -16,7 +16,10 @@
16#include "tool_util.h"16#include "tool_util.h"
17 17 
18#include <algorithm>18#include <algorithm>
19+#include <cerrno>
19#include <climits>20#include <climits>
21+#include <cstdlib>
22+#include <cstdint>
20#include <nlohmann/json.hpp>23#include <nlohmann/json.hpp>
21#include <random>24#include <random>
22#include <set>25#include <set>
@@ -25,10 +28,12 @@
25#include <vector>28#include <vector>
26 29 
27#include "accesstoken_kit.h"30#include "accesstoken_kit.h"
31+#include "array_wrapper.h"
28#include "bundle_info.h"32#include "bundle_info.h"
29#include "bundle_mgr_helper.h"33#include "bundle_mgr_helper.h"
30#include "cli_error_code.h"34#include "cli_error_code.h"
31#include "cli_event_report.h"35#include "cli_event_report.h"
36+#include "double_wrapper.h"
32#include "exec_cmd_param.h"37#include "exec_cmd_param.h"
33#include "exec_tool_param.h"38#include "exec_tool_param.h"
34#include "hilog_tag_wrapper.h"39#include "hilog_tag_wrapper.h"
@@ -43,7 +48,6 @@
43#include "want_params_wrapper.h"48#include "want_params_wrapper.h"
44#include "bool_wrapper.h"49#include "bool_wrapper.h"
45#include "int_wrapper.h"50#include "int_wrapper.h"
46-#include "string_wrapper.h"
47 51 
48namespace OHOS {52namespace OHOS {
49namespace CliTool {53namespace CliTool {
@@ -311,6 +315,325 @@ void ToolUtil::TransferToCmdParam(const AAFwk::WantParams &args, std::vector<std
311 }315 }
312}316}
313 317 
318+// ============================================================================
319+// ParseToolCommand: parse a concatenated tool command string (tool command mode)
320+// Supported syntax (long-flag only):
321+// <toolName> [subcommand] [--key=value | --key value | --flag | --flag false]...
322+// Single-quoted values are kept as one token and the quotes are stripped.
323+// Repeated flags of the same key are collected into an array.
324+// ============================================================================
325+ 
326+namespace {
327+constexpr char FLAG_PREFIX[] = "--";
328+constexpr size_t FLAG_PREFIX_LEN = 2;
329+constexpr char HELP_KEY[] = "help";
330+// Argument start index: tokens[0] is always toolName.
331+constexpr size_t ARG_START_TOOLNAME_ONLY = 1; // no subcommand
332+constexpr size_t ARG_START_WITH_SUBCOMMAND = 2; // tokens[1] is subcommand
333+ 
334+struct ParsedCommandArgs {
335+ std::map<std::string, std::vector<std::string>> values;
336+};
337+ 
338+struct SubCommandResult {
339+ std::string inputSchema;
340+ size_t argStart = ARG_START_TOOLNAME_ONLY;
341+ std::string subcommand;
342+};
343+ 
344+bool TokenizeCommand(const std::string &cmd, std::vector<std::string> &tokens)
345+{
H
Hhwliujinwei1 天前

[次要] TokenizeCommand 仅处理单引号,不支持双引号和转义字符。对于包含双引号或嵌套引号的命令字符串可能解析错误。建议在注释或文档中明确说明支持的语法范围,或补充对双引号和反斜杠转义的支持。

likedislike
346+ std::string current;
347+ bool inQuote = false;
348+ for (char c : cmd) {
349+ if (c == '\'') {
350+ inQuote = !inQuote;
351+ continue;
352+ }
353+ if ((c == ' ' || c == '\t') && !inQuote) {
354+ if (!current.empty()) {
355+ tokens.push_back(current);
356+ current.clear();
357+ }
358+ continue;
359+ }
360+ current.push_back(c);
361+ }
362+ if (inQuote) {
363+ return false;
364+ }
365+ if (!current.empty()) {
366+ tokens.push_back(current);
367+ }
368+ return true;
369+}
370+ 
371+bool IsFlagToken(const std::string &token)
372+{
373+ return token.size() >= FLAG_PREFIX_LEN && token.compare(0, FLAG_PREFIX_LEN, FLAG_PREFIX) == 0;
374+}
375+ 
376+bool IsBooleanSchema(const nlohmann::json &properties, const std::string &key)
377+{
378+ auto it = properties.find(key);
379+ if (it == properties.end() || !it->contains("type") || !it->at("type").is_string()) {
380+ return false;
381+ }
382+ return it->at("type").get<std::string>() == "boolean";
383+}
384+ 
385+int32_t ParseArgTokens(const std::vector<std::string> &tokens, size_t start,
386+ const nlohmann::json &properties, ParsedCommandArgs &parsed, std::string &detail)
387+{
388+ for (size_t i = start; i < tokens.size(); ++i) {
389+ const std::string &token = tokens[i];
390+ if (!IsFlagToken(token)) {
391+ // Bare token without "--" prefix (short flags / custom prefix) is not supported.
392+ detail = DETAIL_PARAM_NOT_FOUND;
393+ return ERR_INVALID_PARAM;
394+ }
395+ std::string flagText = token.substr(FLAG_PREFIX_LEN);
396+ auto eqPos = flagText.find('=');
397+ std::string key = (eqPos == std::string::npos) ? flagText : flagText.substr(0, eqPos);
398+ if (key.empty()) {
399+ detail = DETAIL_PARAM_NOT_FOUND;
400+ return ERR_INVALID_PARAM;
401+ }
402+ // "help" is always allowed, matching ValidateInputSchemaProperties.
403+ if (key == HELP_KEY) {
404+ parsed.values[key].push_back("true");
405+ continue;
406+ }
407+ if (!properties.contains(key)) {
408+ detail = DETAIL_PARAM_NOT_FOUND;
409+ return ERR_INVALID_PARAM;
410+ }
411+ // --key=value
412+ if (eqPos != std::string::npos) {
413+ parsed.values[key].push_back(flagText.substr(eqPos + 1));
414+ continue;
415+ }
416+ // --key with no inline value: decide whether it takes a value or is a boolean flag.
417+ bool isBool = IsBooleanSchema(properties, key);
418+ if (i + 1 >= tokens.size() || IsFlagToken(tokens[i + 1])) {
419+ if (!isBool) {
420+ detail = DETAIL_PARAM_TYPE_MISMATCH;
421+ return ERR_INVALID_PARAM;
422+ }
423+ parsed.values[key].push_back("true");
424+ continue;
425+ }
426+ // --key false (explicit boolean false)
427+ if (isBool && tokens[i + 1] == "false") {
428+ parsed.values[key].push_back("false");
429+ ++i;
430+ continue;
431+ }
432+ // --key value
433+ parsed.values[key].push_back(tokens[i + 1]);
434+ ++i;
435+ }
436+ return ERR_OK;
437+}
438+ 
439+bool ConvertRawValue(const std::string &raw, const std::string &type, sptr<AAFwk::IInterface> &value)
440+{
441+ if (type == "boolean") {
442+ if (raw == "true") {
443+ value = AAFwk::Boolean::Box(true);
444+ return true;
445+ }
446+ if (raw == "false") {
447+ value = AAFwk::Boolean::Box(false);
448+ return true;
449+ }
450+ return false;
451+ }
452+ if (type == "integer") {
453+ errno = 0;
454+ char *end = nullptr;
455+ long long intVal = std::strtoll(raw.c_str(), &end, 10);
456+ if (errno != 0 || end == raw.c_str() || *end != '\0' ||
457+ intVal < INT32_MIN || intVal > INT32_MAX) {
458+ return false;
459+ }
460+ value = AAFwk::Integer::Box(static_cast<int32_t>(intVal));
461+ return true;
462+ }
463+ if (type == "number") {
464+ errno = 0;
465+ char *end = nullptr;
466+ double dblVal = std::strtod(raw.c_str(), &end);
467+ if (errno != 0 || end == raw.c_str() || *end != '\0') {
468+ return false;
469+ }
470+ value = AAFwk::Double::Box(dblVal);
471+ return true;
472+ }
473+ // string and other unknown types are kept as string (compatible with
474+ // ValidateBasicType which allows unknown types).
475+ value = AAFwk::String::Box(raw);
476+ return true;
477+}
478+ 
479+AAFwk::InterfaceID GetArrayTypeId(const std::string &type)
480+{
481+ if (type == "boolean") {
482+ return AAFwk::g_IID_IBoolean;
483+ }
484+ if (type == "integer") {
485+ return AAFwk::g_IID_IInteger;
486+ }
487+ if (type == "number") {
488+ return AAFwk::g_IID_IDouble;
489+ }
490+ return AAFwk::g_IID_IString;
491+}
492+ 
493+int32_t ConvertArrayArg(const nlohmann::json &prop, const std::vector<std::string> &values,
494+ AAFwk::WantParams &args, const std::string &key, std::string &detail)
495+{
496+ std::string itemType = "string";
497+ if (prop.contains("items") && prop["items"].is_object() &&
498+ prop["items"].contains("type") && prop["items"]["type"].is_string()) {
499+ itemType = prop["items"]["type"].get<std::string>();
500+ }
501+ sptr<AAFwk::IArray> array = new (std::nothrow) AAFwk::Array(
502+ static_cast<long>(values.size()), GetArrayTypeId(itemType));
503+ if (array == nullptr) {
504+ detail = DETAIL_PARAM_TYPE_MISMATCH;
505+ return ERR_INVALID_PARAM;
506+ }
507+ for (size_t i = 0; i < values.size(); ++i) {
508+ sptr<AAFwk::IInterface> element;
509+ if (!ConvertRawValue(values[i], itemType, element)) {
510+ detail = DETAIL_PARAM_TYPE_MISMATCH;
511+ return ERR_INVALID_PARAM;
512+ }
513+ array->Set(static_cast<long>(i), element);
514+ }
515+ args.SetParam(key, array);
516+ return ERR_OK;
517+}
518+ 
519+int32_t ConvertParsedArgs(const ParsedCommandArgs &parsed, const nlohmann::json &properties,
520+ AAFwk::WantParams &args, std::string &detail)
521+{
522+ for (const auto &[key, values] : parsed.values) {
523+ if (key == HELP_KEY) {
524+ args.SetParam(key, AAFwk::Boolean::Box(true));
525+ continue;
526+ }
527+ auto propIt = properties.find(key);
528+ if (propIt == properties.end()) {
529+ continue;
530+ }
531+ const nlohmann::json &prop = propIt.value();
532+ std::string type = "string";
533+ if (prop.contains("type") && prop["type"].is_string()) {
534+ type = prop["type"].get<std::string>();
535+ }
536+ if (type == "array") {
537+ if (ConvertArrayArg(prop, values, args, key, detail) != ERR_OK) {
538+ return ERR_INVALID_PARAM;
539+ }
540+ continue;
541+ }
542+ // Non-array parameter must not be repeated.
543+ if (values.size() > 1) {
544+ detail = DETAIL_PARAM_TYPE_MISMATCH;
545+ return ERR_INVALID_PARAM;
546+ }
547+ sptr<AAFwk::IInterface> value;
548+ if (!ConvertRawValue(values[0], type, value)) {
549+ detail = DETAIL_PARAM_TYPE_MISMATCH;
550+ return ERR_INVALID_PARAM;
551+ }
552+ args.SetParam(key, value);
553+ }
554+ return ERR_OK;
555+}
556+ 
557+int32_t ResolveSubCommand(const std::vector<std::string> &tokens, const ToolInfo &toolInfo,
558+ SubCommandResult &result, std::string &detail)
559+{
560+ result.inputSchema = toolInfo.inputSchema;
561+ result.argStart = ARG_START_TOOLNAME_ONLY;
562+ if (tokens.size() <= 1 || IsFlagToken(tokens[1])) {
563+ return ERR_OK;
564+ }
565+ if (!toolInfo.hasSubCommand) {
566+ TAG_LOGE(AAFwkTag::CLI_TOOL, "tool has no subcommand");
567+ detail = DETAIL_SUBCOMMAND_NOT_FOUND;
568+ return ERR_TOOL_NOT_EXIST;
569+ }
570+ auto it = toolInfo.subcommands.find(tokens[1]);
571+ if (it == toolInfo.subcommands.end()) {
572+ TAG_LOGE(AAFwkTag::CLI_TOOL, "subcommand not found: %{public}s", tokens[1].c_str());
573+ detail = DETAIL_SUBCOMMAND_NOT_FOUND;
574+ return ERR_TOOL_NOT_EXIST;
575+ }
576+ result.subcommand = tokens[1];
577+ result.inputSchema = it->second.inputSchema;
578+ result.argStart = ARG_START_WITH_SUBCOMMAND;
579+ return ERR_OK;
580+}
581+} // namespace
582+ 
583+int32_t ToolUtil::ParseToolCommand(const std::string &cmd, const ToolInfo &toolInfo,
584+ ExecToolParam &param, std::string &detail)
585+{
586+ // Stage 0: tokenize.
587+ std::vector<std::string> tokens;
588+ if (!TokenizeCommand(cmd, tokens) || tokens.empty()) {
589+ TAG_LOGE(AAFwkTag::CLI_TOOL, "invalid tool command string");
590+ detail = DETAIL_PARAM_NOT_FOUND;
591+ return ERR_INVALID_PARAM;
592+ }
593+ 
594+ // Stage 1: validate toolName.
595+ const std::string &toolName = tokens[0];
596+ if (toolName.empty() || toolName[0] == '/') {
597+ TAG_LOGE(AAFwkTag::CLI_TOOL, "invalid toolName");
598+ detail = DETAIL_PARAM_NOT_FOUND;
599+ return ERR_INVALID_PARAM;
600+ }
601+ if (!toolInfo.name.empty() && toolInfo.name != toolName) {
602+ TAG_LOGE(AAFwkTag::CLI_TOOL, "tool not found: %{public}s", toolName.c_str());
603+ detail = DETAIL_TOOL_NOT_FOUND;
604+ return ERR_TOOL_NOT_EXIST;
605+ }
606+ param.toolName = toolName;
607+ 
608+ // Stage 2: resolve subcommand.
609+ SubCommandResult subResult;
610+ if (auto ret = ResolveSubCommand(tokens, toolInfo, subResult, detail); ret != ERR_OK) {
611+ return ret;
612+ }
613+ param.subcommand = std::move(subResult.subcommand);
614+ const std::string &inputSchema = subResult.inputSchema;
615+ size_t argStart = subResult.argStart;
616+ 
617+ // Load properties from schema.
618+ nlohmann::json properties = nlohmann::json::object();
619+ if (!inputSchema.empty()) {
620+ nlohmann::json schema = nlohmann::json::parse(inputSchema, nullptr, false);
621+ if (!schema.is_discarded() && schema.contains("properties") && schema["properties"].is_object()) {
622+ properties = schema["properties"];
623+ }
624+ }
625+ 
626+ // Stage 3: parse arguments.
627+ ParsedCommandArgs parsed;
628+ auto res = ParseArgTokens(tokens, argStart, properties, parsed, detail);
629+ if (res != ERR_OK) {
630+ return res;
631+ }
632+ 
633+ // Stage 4: type recovery.
634+ return ConvertParsedArgs(parsed, properties, param.args, detail);
635+}
636+ 
314void ToolUtil::ProcessBooleanParam(const std::string &key, const sptr<AAFwk::IInterface> &value,637void ToolUtil::ProcessBooleanParam(const std::string &key, const sptr<AAFwk::IInterface> &value,
315 std::vector<std::string> &execArgs)638 std::vector<std::string> &execArgs)
316{639{