From 83faeeb42eb9d2d85f2905772f17889ae21c8587 Mon Sep 17 00:00:00 2001 From: xhz-sz Date: Mon, 6 Jul 2026 14:51:22 +0800 Subject: [PATCH] Add serialization and parsing capabilities for wantParams Signed-off-by: xhz-sz --- .../wantagent/include/want_agent_helper.h | 52 ++++++ .../wantagent/src/want_agent_helper.cpp | 124 +++++++++++++++ .../want_agent_helper_test.cpp | 148 ++++++++++++++++++ 3 files changed, 324 insertions(+) diff --git a/interfaces/inner_api/wantagent/include/want_agent_helper.h b/interfaces/inner_api/wantagent/include/want_agent_helper.h index 49e05a3c6b..b9c9d8cae9 100644 --- a/interfaces/inner_api/wantagent/include/want_agent_helper.h +++ b/interfaces/inner_api/wantagent/include/want_agent_helper.h @@ -205,6 +205,58 @@ public: */ static std::shared_ptr FromString(const std::string &jsonString, int32_t uid = -1); + /** + * Convert WantAgentInfo object to string with WantParams envelope for extraInfo. + * This function is the upgrade path for ToString when extraInfo must round + * trip safely. ToString stores extraInfo through WantParamWrapper::ToString, + * which uses manual string concatenation and inherits the legacy + * WantParamsWrapper escaping and parsing limitations. This function stores + * extraInfo through WantParamWrapperJson::Serialize so the WantParams payload + * is wrapped by the envelope format and JSON escaping is applied. + * + * The output keeps the same outer WantAgent string format as ToString, but + * extraInfo.extraInfoValue is generated by WantParamWrapperJson::Serialize: + * {"extraInfo":{"extraInfoValue":"{\"ohos.want.paramsStringEnvelope\":{...}}"}} + * + * @param agent WantAgent object. + * @return WantAgentInfo object's serialized string. + */ + static std::string ToStringWithEnvelope(const std::shared_ptr &agent); + + /** + * @brief Parses a WantAgent string generated by ToStringWithEnvelope. + * + * Convert string with WantParams envelope extraInfo to WantAgentInfo object. + * This function is the upgrade path for FromString when the input was + * generated by ToStringWithEnvelope. FromString parses extraInfo with + * WantParamWrapper::Parse and therefore has the same legacy limitations: + * unescaped quotes/backslashes/control characters, delimiter-like content, + * manually embedded typeId text, unsupported types, and some truncated or + * trailing inputs may fail or restore incorrectly. + * + * Before calling this function, callers must use HasWantParamsEnvelope + * to check that the input string is generated by ToStringWithEnvelope, + * unless the data source is already guaranteed to be generated by + * ToStringWithEnvelope. + * + * @param jsonString String to parse. + * @return WantAgentInfo object. + */ + static std::shared_ptr FromStringWithEnvelope( + const std::string &jsonString, int32_t uid = -1); + + /** + * Check whether the input string contains WantParams envelope in extraInfo. + * + * This function is used to distinguish data generated by + * ToStringWithEnvelope from legacy data generated by ToString. + * + * @param jsonString String to check. + * @return Returns true if the WantParamWrapperJson envelope is detected; + * returns false otherwise. + */ + static bool HasWantParamsEnvelope(const std::string &jsonString); + private: WantAgentHelper(); virtual ~WantAgentHelper() = default; diff --git a/interfaces/inner_api/wantagent/src/want_agent_helper.cpp b/interfaces/inner_api/wantagent/src/want_agent_helper.cpp index 89e76f201d..3fa4a920d8 100644 --- a/interfaces/inner_api/wantagent/src/want_agent_helper.cpp +++ b/interfaces/inner_api/wantagent/src/want_agent_helper.cpp @@ -21,6 +21,7 @@ #include "local_pending_want.h" #include "multi_app_utils.h" #include "want_params_wrapper.h" +#include "want_params_wrapper_json.h" #include "pending_want.h" #include "permission_verification.h" #include "want_agent_client.h" @@ -574,6 +575,129 @@ std::shared_ptr WantAgentHelper::FromString(const std::string &jsonSt return GetWantAgent(info, userId, uid); } +std::string WantAgentHelper::ToStringWithEnvelope(const std::shared_ptr &agent) +{ + if (agent == nullptr) { + TAG_LOGE(AAFwkTag::WANTAGENT, "invalid param"); + return ""; + } + + std::shared_ptr pendingWant = agent->GetPendingWant(); + if (pendingWant == nullptr) { + TAG_LOGE(AAFwkTag::WANTAGENT, "invalid param"); + return ""; + } + + std::shared_ptr info = pendingWant->GetWantSenderInfo(pendingWant->GetTarget()); + if (info == nullptr) { + TAG_LOGE(AAFwkTag::WANTAGENT, "invalid param"); + return ""; + } + nlohmann::json jsonObject; + jsonObject["requestCode"] = (*info.get()).requestCode; + jsonObject["operationType"] = (*info.get()).type; + jsonObject["flags"] = (*info.get()).flags; + jsonObject["userId"] = (*info.get()).userId; + jsonObject["appIndex"] = (*info.get()).appIndex; + + nlohmann::json wants = nlohmann::json::array(); + for (auto &wantInfo : (*info.get()).allWants) { + wants.emplace_back(wantInfo.want.ToString()); + } + jsonObject["wants"] = wants; + + if ((*info.get()).allWants.size() > 0) { + nlohmann::json paramsObj; + std::string paramsString; + if (!AAFwk::WantParamWrapperJson::Serialize((*info.get()).allWants[0].want.GetParams(), paramsString)) { + TAG_LOGE(AAFwkTag::WANTAGENT, "serialize want params json failed"); + } + paramsObj["extraInfoValue"] = paramsString; + jsonObject["extraInfo"] = paramsObj; + } + + return jsonObject.dump(); +} + +std::shared_ptr WantAgentHelper::FromStringWithEnvelope( + const std::string &jsonString, int32_t uid) +{ + if (jsonString.empty()) { + return nullptr; + } + nlohmann::json jsonObject = nlohmann::json::parse(jsonString, nullptr, false); + if (jsonObject.is_discarded()) { + TAG_LOGE(AAFwkTag::WANTAGENT, "Failed to parse json string"); + return nullptr; + } + int requestCode = -1; + if (jsonObject.contains("requestCode") && jsonObject["requestCode"].is_number_integer()) { + requestCode = jsonObject.at("requestCode").get(); + } + + int userId = -1; + if (jsonObject.contains("userId") && jsonObject["userId"].is_number_integer()) { + userId = jsonObject.at("userId").get(); + } + + WantAgentConstant::OperationType operationType = WantAgentConstant::OperationType::UNKNOWN_TYPE; + if (jsonObject.contains("operationType") && jsonObject["operationType"].is_number_integer()) { + operationType = static_cast(jsonObject.at("operationType").get()); + } + + std::vector flagsVec = ParseFlags(jsonObject); + std::vector> wants = {}; + if (jsonObject.contains("wants") && jsonObject["wants"].is_array()) { + for (auto &wantObj : jsonObject.at("wants")) { + if (wantObj.is_string()) { + auto wantString = wantObj.get(); + wants.emplace_back(std::make_shared(*Want::FromString(wantString))); + } + } + } + + std::shared_ptr extraInfo = nullptr; + if (jsonObject.contains("extraInfo") && jsonObject["extraInfo"].is_object()) { + auto extraInfoObj = jsonObject.at("extraInfo"); + if (extraInfoObj.contains("extraInfoValue") && extraInfoObj["extraInfoValue"].is_string()) { + AAFwk::WantParams params; + if (!AAFwk::WantParamWrapperJson::Parse(extraInfoObj.at("extraInfoValue").get(), params)) { + TAG_LOGE(AAFwkTag::WANTAGENT, "parse want params json failed"); + } + extraInfo = std::make_shared(params); + } + } + + int32_t appIndex = 0; + if (jsonObject.contains("appIndex") && jsonObject["appIndex"].is_number_integer()) { + appIndex = jsonObject.at("appIndex").get(); + } + + WantAgentInfo info(requestCode, appIndex, operationType, flagsVec, wants, extraInfo); + return GetWantAgent(info, userId, uid); +} + +bool WantAgentHelper::HasWantParamsEnvelope(const std::string &jsonString) +{ + if (jsonString.empty()) { + return false; + } + + nlohmann::json jsonObject = nlohmann::json::parse(jsonString, nullptr, false); + if (jsonObject.is_discarded() || !jsonObject.is_object()) { + return false; + } + if (!jsonObject.contains("extraInfo") || !jsonObject["extraInfo"].is_object()) { + return false; + } + + const auto &extraInfoObj = jsonObject["extraInfo"]; + if (!extraInfoObj.contains("extraInfoValue") || !extraInfoObj["extraInfoValue"].is_string()) { + return false; + } + return AAFwk::WantParamWrapperJson::HasEnvelope(extraInfoObj["extraInfoValue"].get()); +} + std::vector WantAgentHelper::ParseFlags(nlohmann::json jsonObject) { int flags = -1; diff --git a/test/unittest/want_agent_helper_test/want_agent_helper_test.cpp b/test/unittest/want_agent_helper_test/want_agent_helper_test.cpp index 8e0c090167..b72432f910 100644 --- a/test/unittest/want_agent_helper_test/want_agent_helper_test.cpp +++ b/test/unittest/want_agent_helper_test/want_agent_helper_test.cpp @@ -1533,4 +1533,152 @@ HWTEST_F(WantAgentHelperTest, GetWantFromProxy_0100, TestSize.Level1) auto want = WantAgentHelper::GetWantFromProxy(wantAgent); EXPECT_EQ(want, nullptr); } + +/* + * @tc.number : WantAgentHelper_6700 + * @tc.name : WantAgentHelper ToStringWithEnvelope + * @tc.desc : Test ToStringWithEnvelope uses WantParamWrapperJson without changing ToString format. + */ +HWTEST_F(WantAgentHelperTest, WantAgentHelper_6700, Function | MediumTest | Level1) +{ + std::shared_ptr want = std::make_shared(); + ElementName element("device", "bundleName", "abilityNameToJson"); + want->SetElement(element); + WantAgentInfo wantAgentInfo; + wantAgentInfo.wants_.emplace_back(want); + wantAgentInfo.operationType_ = WantAgentConstant::OperationType::START_ABILITY; + wantAgentInfo.requestCode_ = 10; + std::shared_ptr wParams = std::make_shared(); + wParams->SetParam("key", Boolean::Box(true)); + wantAgentInfo.extraInfo_ = wParams; + auto wantAgent = WantAgentHelper::GetWantAgent(wantAgentInfo); + ASSERT_NE(wantAgent, nullptr); + + auto legacyString = WantAgentHelper::ToString(wantAgent); + auto jsonString = WantAgentHelper::ToStringWithEnvelope(wantAgent); + ASSERT_FALSE(legacyString.empty()); + ASSERT_FALSE(jsonString.empty()); + + auto legacyObject = nlohmann::json::parse(legacyString, nullptr, false); + auto jsonObject = nlohmann::json::parse(jsonString, nullptr, false); + ASSERT_FALSE(legacyObject.is_discarded()); + ASSERT_FALSE(jsonObject.is_discarded()); + auto legacyExtraInfo = legacyObject.at("extraInfo").at("extraInfoValue").get(); + auto jsonExtraInfo = jsonObject.at("extraInfo").at("extraInfoValue").get(); + EXPECT_EQ(legacyExtraInfo.find("ohos.want.paramsStringEnvelope"), std::string::npos); + EXPECT_NE(jsonExtraInfo.find("ohos.want.paramsStringEnvelope"), std::string::npos); + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope(legacyString)); + EXPECT_TRUE(WantAgentHelper::HasWantParamsEnvelope(jsonString)); +} + +/* + * @tc.number : WantAgentHelper_6710 + * @tc.name : WantAgentHelper FromStringWithEnvelope + * @tc.desc : Test FromStringWithEnvelope restores WantAgent from WantParamWrapperJson extraInfo. + */ +HWTEST_F(WantAgentHelperTest, WantAgentHelper_6710, Function | MediumTest | Level1) +{ + std::shared_ptr want = std::make_shared(); + ElementName element("device", "bundleName", "abilityNameFromJson"); + want->SetElement(element); + WantAgentInfo wantAgentInfo; + wantAgentInfo.wants_.emplace_back(want); + wantAgentInfo.operationType_ = WantAgentConstant::OperationType::START_ABILITY; + wantAgentInfo.requestCode_ = 10; + std::shared_ptr wParams = std::make_shared(); + wParams->SetParam("key", Boolean::Box(true)); + wantAgentInfo.extraInfo_ = wParams; + auto wantAgent = WantAgentHelper::GetWantAgent(wantAgentInfo); + ASSERT_NE(wantAgent, nullptr); + + auto jsonString = WantAgentHelper::ToStringWithEnvelope(wantAgent); + auto restoredWantAgent = WantAgentHelper::FromStringWithEnvelope(jsonString); + ASSERT_NE(restoredWantAgent, nullptr); + + auto restoredWant = WantAgentHelper::GetWant(restoredWantAgent); + ASSERT_NE(restoredWant, nullptr); + auto value = restoredWant->GetParams().GetParam("key"); + auto boolValue = IBoolean::Query(value); + ASSERT_NE(boolValue, nullptr); + EXPECT_TRUE(Boolean::Unbox(boolValue)); +} + +/* + * @tc.number : WantAgentHelper_6720 + * @tc.name : WantAgentHelper ToStringWithEnvelope invalid input + * @tc.desc : Test ToStringWithEnvelope invalid agent branches. + */ +HWTEST_F(WantAgentHelperTest, WantAgentHelper_6720, Function | MediumTest | Level1) +{ + EXPECT_TRUE(WantAgentHelper::ToStringWithEnvelope(nullptr).empty()); + + auto wantAgentWithoutPendingWant = std::make_shared(std::shared_ptr(nullptr)); + EXPECT_TRUE(WantAgentHelper::ToStringWithEnvelope(wantAgentWithoutPendingWant).empty()); + + auto pendingWantWithoutTarget = std::make_shared(sptr(nullptr)); + auto wantAgentWithoutInfo = std::make_shared(pendingWantWithoutTarget); + EXPECT_TRUE(WantAgentHelper::ToStringWithEnvelope(wantAgentWithoutInfo).empty()); +} + +/* + * @tc.number : WantAgentHelper_6730 + * @tc.name : WantAgentHelper FromStringWithEnvelope invalid input + * @tc.desc : Test FromStringWithEnvelope invalid string and malformed field branches. + */ +HWTEST_F(WantAgentHelperTest, WantAgentHelper_6730, Function | MediumTest | Level1) +{ + EXPECT_EQ(WantAgentHelper::FromStringWithEnvelope(""), nullptr); + EXPECT_EQ(WantAgentHelper::FromStringWithEnvelope("{"), nullptr); + EXPECT_EQ(WantAgentHelper::FromStringWithEnvelope(R"({"requestCode":"bad","wants":[]})"), + nullptr); + EXPECT_EQ(WantAgentHelper::FromStringWithEnvelope(R"({"wants":[100]})"), nullptr); + + Want want; + ElementName element("device", "bundleName", "abilityNameFromJsonInvalid"); + want.SetElement(element); + const auto originParamSize = want.GetParams().Size(); + nlohmann::json jsonObject; + jsonObject["requestCode"] = 6730; + jsonObject["operationType"] = static_cast(WantAgentConstant::OperationType::START_ABILITY); + jsonObject["flags"] = static_cast(FLAG_UPDATE_CURRENT); + jsonObject["userId"] = -1; + jsonObject["appIndex"] = "bad"; + jsonObject["wants"] = nlohmann::json::array({ want.ToString() }); + jsonObject["extraInfo"] = { { "extraInfoValue", "invalid want params json" } }; + + auto wantAgent = WantAgentHelper::FromStringWithEnvelope(jsonObject.dump()); + ASSERT_NE(wantAgent, nullptr); + auto restoredWant = WantAgentHelper::GetWant(wantAgent); + ASSERT_NE(restoredWant, nullptr); + EXPECT_EQ(restoredWant->GetParams().Size(), originParamSize); + + jsonObject["requestCode"] = 6731; + jsonObject["extraInfo"] = "bad"; + EXPECT_NE(WantAgentHelper::FromStringWithEnvelope(jsonObject.dump()), nullptr); + + jsonObject["requestCode"] = 6732; + jsonObject["extraInfo"] = { { "extraInfoValue", 100 } }; + EXPECT_NE(WantAgentHelper::FromStringWithEnvelope(jsonObject.dump()), nullptr); +} + +/* + * @tc.number : WantAgentHelper_6740 + * @tc.name : WantAgentHelper HasWantParamsEnvelope + * @tc.desc : Test HasWantParamsEnvelope dispatches only when extraInfoValue uses WantParamWrapperJson envelope. + */ +HWTEST_F(WantAgentHelperTest, WantAgentHelper_6740, Function | MediumTest | Level1) +{ + nlohmann::json jsonObject; + jsonObject["extraInfo"] = { { "extraInfoValue", R"( {"ohos.want.paramsStringEnvelope":{}} )" } }; + EXPECT_TRUE(WantAgentHelper::HasWantParamsEnvelope(jsonObject.dump())); + + jsonObject["extraInfo"] = { { "extraInfoValue", "legacy want params" } }; + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope(jsonObject.dump())); + + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope("")); + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope("{")); + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope(R"({"extraInfo":"bad"})")); + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope(R"({"extraInfo":{"extraInfoValue":100}})")); + EXPECT_FALSE(WantAgentHelper::HasWantParamsEnvelope(R"({"wants":[]})")); +} } // namespace OHOS::AbilityRuntime::WantAgent