From 3f2b844240645fe009d41412f28bd3e8a413353c Mon Sep 17 00:00:00 2001 From: "Muyuan Li (from Dev Box)" Date: Thu, 22 May 2025 16:12:54 +0800 Subject: [PATCH 1/7] Revert "[Issue #3871] File activation for unpackaged App would fail when file path contains unicode characters or special characters (#5175)" This reverts commit e8d5edff29c744191833931cab2fd65c6b07b0b2. --- dev/AppLifecycle/ExtensionContract.h | 8 ---- dev/AppLifecycle/FileActivatedEventArgs.h | 45 ++--------------------- test/AppLifecycle/FunctionalTests.cpp | 41 +-------------------- 3 files changed, 5 insertions(+), 89 deletions(-) diff --git a/dev/AppLifecycle/ExtensionContract.h b/dev/AppLifecycle/ExtensionContract.h index b60bf5bc30..c2f61d73dd 100644 --- a/dev/AppLifecycle/ExtensionContract.h +++ b/dev/AppLifecycle/ExtensionContract.h @@ -52,14 +52,6 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation } } - // QueryParsed() function would return empty when it's a file contract with unicode characters in file path - // Thus following additional check for file contract is needed - auto fileContractUri = GenerateEncodedLaunchUri(L"App", c_fileContractId); - if (CompareStringOrdinal(uri.AbsoluteUri().c_str(), static_cast(fileContractUri.length()), fileContractUri.c_str(), -1, TRUE) == CSTR_EQUAL) - { - return { ExtendedActivationKind::File, FileActivatedEventArgs::Deserialize(uri) }; - } - return { ExtendedActivationKind::Protocol, nullptr }; } } diff --git a/dev/AppLifecycle/FileActivatedEventArgs.h b/dev/AppLifecycle/FileActivatedEventArgs.h index a03c75b15d..0e0d16aec3 100644 --- a/dev/AppLifecycle/FileActivatedEventArgs.h +++ b/dev/AppLifecycle/FileActivatedEventArgs.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #pragma once @@ -47,46 +47,9 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - auto parsedQuery = uri.QueryParsed(); - - // By design parsed query should have a size of 3 (ContractId, Verb, File), in which case result from QueryParsed() can be used directly. - // However, it may have a size of 0 when uri contains unicode characters, or more than 3 when file path contains "&", which requires manual parsing with string functions. - if (parsedQuery.Size() == 3) - { - auto verb = parsedQuery.GetFirstValueByName(L"Verb"); - auto file = parsedQuery.GetFirstValueByName(L"File"); - return make(verb, file); - } - - const std::wstring query = uri.Query().c_str(); - auto queryLength = query.length(); - - auto verbBegin = query.find(L"&Verb="); - if (verbBegin == std::wstring::npos) - { - throw winrt::hresult_invalid_argument(L"Query of encoded file protocol should contain 'Verb'"); - } - verbBegin += 6; // Length of "&Verb=" - - auto verbEnd = query.find(L"&", verbBegin); - if (verbEnd == std::wstring::npos) - { - verbEnd = queryLength; - } - - auto fileBegin = query.find(L"&File="); - if (fileBegin == std::wstring::npos) - { - throw winrt::hresult_invalid_argument(L"Query of encoded file protocol should contain 'File'"); - } - fileBegin += 6; // Length of "&File=" - - // File path may contain '&' character, so fileEnd can only be assumed to be the end of the query or start of Verb - auto fileEnd = verbBegin > fileBegin ? verbBegin : queryLength; - - auto verb = winrt::to_hstring(query.substr(verbBegin, verbEnd - verbBegin).c_str()); - auto file = winrt::to_hstring(query.substr(fileBegin, fileEnd - fileBegin).c_str()); - + auto query = uri.QueryParsed(); + auto verb = query.GetFirstValueByName(L"Verb"); + auto file = query.GetFirstValueByName(L"File"); return make(verb, file); } diff --git a/test/AppLifecycle/FunctionalTests.cpp b/test/AppLifecycle/FunctionalTests.cpp index 3f969ee795..a037eb907f 100644 --- a/test/AppLifecycle/FunctionalTests.cpp +++ b/test/AppLifecycle/FunctionalTests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #include "pch.h" @@ -29,7 +29,6 @@ namespace Test::AppLifecycle std::wstring m_testName; const std::wstring c_testDataFileName = L"testfile" + c_testFileExtension; - const std::wstring c_testDataFileName_Unicode = L"你好&世界" + c_testFileExtension; const std::wstring c_testDataFileName_Packaged = L"testfile" + c_testFileExtension_Packaged; const std::wstring c_testPackageFile = g_deploymentDir + L"AppLifecycleTestPackage.msix"; const std::wstring c_testVCLibsPackageFile = g_deploymentDir + L"VCLibs.appx"; @@ -53,7 +52,6 @@ namespace Test::AppLifecycle // Write out some test content. WriteContentFile(c_testDataFileName); - WriteContentFile(c_testDataFileName_Unicode); WriteContentFile(c_testDataFileName_Packaged); return true; @@ -65,7 +63,6 @@ namespace Test::AppLifecycle try { DeleteContentFile(c_testDataFileName_Packaged); - DeleteContentFile(c_testDataFileName_Unicode); DeleteContentFile(c_testDataFileName); UninstallPackage(c_testPackageFullName); } @@ -131,42 +128,6 @@ namespace Test::AppLifecycle WaitForEvent(event, m_failed); } - TEST_METHOD(GetActivatedEventArgsForUnicodeNamedFile_Win32) - { - // Create a named event for communicating with test app. - auto event = CreateTestEvent(c_testFilePhaseEventName); - - // Cleanup any leftover data from previous runs i.e. ensure we running with a clean slate - try - { - Execute(L"AppLifecycleTestApp.exe", L"/UnregisterFile", g_deploymentDir); - WaitForEvent(event, m_failed); - } - catch (...) - { - //TODO:Unregister should not fail if ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND - } - - // Launch the test app to register for protocol launches. - Execute(L"AppLifecycleTestApp.exe", L"/RegisterFile", g_deploymentDir); - - // Wait for the register event. - WaitForEvent(event, m_failed); - - // Launch the file and wait for the event to fire. - auto file = OpenDocFile(c_testDataFileName_Unicode); - auto launchResult = Launcher::LaunchFileAsync(file).get(); - VERIFY_IS_TRUE(launchResult); - - // Wait for the file activation. - WaitForEvent(event, m_failed); - - Execute(L"AppLifecycleTestApp.exe", L"/UnregisterFile", g_deploymentDir); - - // Wait for the unregister event. - WaitForEvent(event, m_failed); - } - TEST_METHOD(GetActivatedEventArgsForFile_PackagedWin32) { // Create a named event for communicating with test app. From 935e780703ab5715ca4d4660ce08c01d9f02dcba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 08:17:43 +0000 Subject: [PATCH 2/7] Initial plan for issue From 2a9afc64fd7c098ef54c3d294600e42b87860da0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 08:20:46 +0000 Subject: [PATCH 3/7] Fix FileActivatedEventArgs to properly unescape Unicode file paths Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- dev/AppLifecycle/FileActivatedEventArgs.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dev/AppLifecycle/FileActivatedEventArgs.h b/dev/AppLifecycle/FileActivatedEventArgs.h index 0e0d16aec3..186252aa24 100644 --- a/dev/AppLifecycle/FileActivatedEventArgs.h +++ b/dev/AppLifecycle/FileActivatedEventArgs.h @@ -50,7 +50,11 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation auto query = uri.QueryParsed(); auto verb = query.GetFirstValueByName(L"Verb"); auto file = query.GetFirstValueByName(L"File"); - return make(verb, file); + + // Unescape the file path to handle Unicode characters + auto unescapedFile = winrt::Windows::Foundation::Uri::UnescapeComponent(file); + + return make(verb, unescapedFile); } // IInternalValueMarshalable From e7a6ef58d8f0265bb7eb240add4cda5896acec8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 08:50:08 +0000 Subject: [PATCH 4/7] Improve URI decoding for files with Unicode characters Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- dev/AppLifecycle/ExtensionContract.h | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/dev/AppLifecycle/ExtensionContract.h b/dev/AppLifecycle/ExtensionContract.h index c2f61d73dd..eca8290f5c 100644 --- a/dev/AppLifecycle/ExtensionContract.h +++ b/dev/AppLifecycle/ExtensionContract.h @@ -37,14 +37,21 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation inline std::tuple DecodeActivatedEventArgs(winrt::Windows::Foundation::Uri const& uri) { + // Try to find ContractId in the query parameters for (auto const& pair : uri.QueryParsed()) { if (CompareStringOrdinal(pair.Name().c_str(), -1, c_contractIdKeyName, -1, TRUE) == CSTR_EQUAL) { auto contractId = pair.Value().c_str(); + + // Unescape the contractId to handle potential Unicode characters + auto unescapedContractId = winrt::Windows::Foundation::Uri::UnescapeComponent(contractId); + for (const auto& extension : c_extensionMap) { - if (CompareStringOrdinal(contractId, -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) + // Try comparing both the original and unescaped contractId + if (CompareStringOrdinal(contractId, -1, extension.contractId, -1, TRUE) == CSTR_EQUAL || + CompareStringOrdinal(unescapedContractId.c_str(), -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) { return { extension.kind, extension.factory(uri) }; } @@ -52,6 +59,20 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation } } + // If we have a File parameter in the query, this is likely a file activation + // This is a fallback for when the ContractId parsing might fail with Unicode characters + auto query = uri.QueryParsed(); + if (query.GetFirstValueByName(L"File") != L"" && query.GetFirstValueByName(L"Verb") != L"") + { + for (const auto& extension : c_extensionMap) + { + if (CompareStringOrdinal(extension.contractId, -1, c_fileContractId, -1, TRUE) == CSTR_EQUAL) + { + return { extension.kind, extension.factory(uri) }; + } + } + } + return { ExtendedActivationKind::Protocol, nullptr }; } } From f3e60dd44a4ae4a9d861250b46218ee0152fc457 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 09:11:49 +0000 Subject: [PATCH 5/7] Implement custom URI query parser to handle Unicode characters Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- dev/AppLifecycle/ExtensionContract.h | 39 +++++----- dev/AppLifecycle/FileActivatedEventArgs.h | 13 ++-- dev/AppLifecycle/LaunchActivatedEventArgs.h | 8 +- dev/AppLifecycle/ProtocolActivatedEventArgs.h | 8 +- dev/AppLifecycle/StartupActivatedEventArgs.h | 8 +- dev/Common/Common.vcxitems | 1 + dev/Common/Common.vcxitems.filters | 3 + dev/Common/UriHelpers.h | 76 +++++++++++++++++++ .../PushNotificationManager.cpp | 20 ++--- 9 files changed, 131 insertions(+), 45 deletions(-) create mode 100644 dev/Common/UriHelpers.h diff --git a/dev/AppLifecycle/ExtensionContract.h b/dev/AppLifecycle/ExtensionContract.h index eca8290f5c..8c44cbe258 100644 --- a/dev/AppLifecycle/ExtensionContract.h +++ b/dev/AppLifecycle/ExtensionContract.h @@ -9,6 +9,7 @@ #include "StartupActivatedEventArgs.h" #include "PushNotificationManager.h" #include "AppNotificationManager.h" +#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -37,32 +38,30 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation inline std::tuple DecodeActivatedEventArgs(winrt::Windows::Foundation::Uri const& uri) { - // Try to find ContractId in the query parameters - for (auto const& pair : uri.QueryParsed()) + // Use custom query parameter parser to handle Unicode characters + auto queryParams = ParseUriQueryParameters(uri); + + // Check for ContractId in the query parameters + auto contractIdIt = queryParams.find(c_contractIdKeyName); + if (contractIdIt != queryParams.end()) { - if (CompareStringOrdinal(pair.Name().c_str(), -1, c_contractIdKeyName, -1, TRUE) == CSTR_EQUAL) + auto contractId = contractIdIt->second; + + for (const auto& extension : c_extensionMap) { - auto contractId = pair.Value().c_str(); - - // Unescape the contractId to handle potential Unicode characters - auto unescapedContractId = winrt::Windows::Foundation::Uri::UnescapeComponent(contractId); - - for (const auto& extension : c_extensionMap) + if (CompareStringOrdinal(contractId.c_str(), -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) { - // Try comparing both the original and unescaped contractId - if (CompareStringOrdinal(contractId, -1, extension.contractId, -1, TRUE) == CSTR_EQUAL || - CompareStringOrdinal(unescapedContractId.c_str(), -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) - { - return { extension.kind, extension.factory(uri) }; - } + return { extension.kind, extension.factory(uri) }; } } } - + // If we have a File parameter in the query, this is likely a file activation - // This is a fallback for when the ContractId parsing might fail with Unicode characters - auto query = uri.QueryParsed(); - if (query.GetFirstValueByName(L"File") != L"" && query.GetFirstValueByName(L"Verb") != L"") + // This is a fallback for when the ContractId parsing might fail + auto fileValue = GetQueryParamValueByName(queryParams, L"File"); + auto verbValue = GetQueryParamValueByName(queryParams, L"Verb"); + + if (!fileValue.empty() && !verbValue.empty()) { for (const auto& extension : c_extensionMap) { @@ -72,7 +71,7 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation } } } - + return { ExtendedActivationKind::Protocol, nullptr }; } } diff --git a/dev/AppLifecycle/FileActivatedEventArgs.h b/dev/AppLifecycle/FileActivatedEventArgs.h index 186252aa24..18dc75bc50 100644 --- a/dev/AppLifecycle/FileActivatedEventArgs.h +++ b/dev/AppLifecycle/FileActivatedEventArgs.h @@ -3,6 +3,7 @@ #pragma once #include "ActivatedEventArgsBase.h" +#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -47,14 +48,12 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - auto query = uri.QueryParsed(); - auto verb = query.GetFirstValueByName(L"Verb"); - auto file = query.GetFirstValueByName(L"File"); + // Use custom query parameter parser to handle Unicode characters + auto queryParams = ParseUriQueryParameters(uri); + auto verb = GetQueryParamValueByName(queryParams, L"Verb"); + auto file = GetQueryParamValueByName(queryParams, L"File"); - // Unescape the file path to handle Unicode characters - auto unescapedFile = winrt::Windows::Foundation::Uri::UnescapeComponent(file); - - return make(verb, unescapedFile); + return make(verb.c_str(), file.c_str()); } // IInternalValueMarshalable diff --git a/dev/AppLifecycle/LaunchActivatedEventArgs.h b/dev/AppLifecycle/LaunchActivatedEventArgs.h index 9ad86b0fcf..d861a3aa5d 100644 --- a/dev/AppLifecycle/LaunchActivatedEventArgs.h +++ b/dev/AppLifecycle/LaunchActivatedEventArgs.h @@ -4,6 +4,7 @@ #include "ActivatedEventArgsBase.h" #include "ValueMarshaling.h" +#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -22,9 +23,10 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - auto query = uri.QueryParsed(); - auto args = query.GetFirstValueByName(L"Arguments"); - return make(args); + // Use custom query parameter parser to handle Unicode characters + auto queryParams = ParseUriQueryParameters(uri); + auto args = GetQueryParamValueByName(queryParams, L"Arguments"); + return make(args.c_str()); } // IInternalValueMarshalable diff --git a/dev/AppLifecycle/ProtocolActivatedEventArgs.h b/dev/AppLifecycle/ProtocolActivatedEventArgs.h index 7caef91d70..f0df81df94 100644 --- a/dev/AppLifecycle/ProtocolActivatedEventArgs.h +++ b/dev/AppLifecycle/ProtocolActivatedEventArgs.h @@ -4,6 +4,7 @@ #include #include "ActivatedEventArgsBase.h" +#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -23,9 +24,10 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - auto query = uri.QueryParsed(); - auto args = query.GetFirstValueByName(L"Uri"); - return make(args); + // Use custom query parameter parser to handle Unicode characters + auto queryParams = ParseUriQueryParameters(uri); + auto args = GetQueryParamValueByName(queryParams, L"Uri"); + return make(args.c_str()); } // IInternalValueMarshalable diff --git a/dev/AppLifecycle/StartupActivatedEventArgs.h b/dev/AppLifecycle/StartupActivatedEventArgs.h index 1d33a3909c..95258aaa16 100644 --- a/dev/AppLifecycle/StartupActivatedEventArgs.h +++ b/dev/AppLifecycle/StartupActivatedEventArgs.h @@ -4,6 +4,7 @@ #include #include "ActivatedEventArgsBase.h" +#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -22,9 +23,10 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - auto query = uri.QueryParsed(); - auto taskId = query.GetFirstValueByName(L"TaskId"); - return make(taskId); + // Use custom query parameter parser to handle Unicode characters + auto queryParams = ParseUriQueryParameters(uri); + auto taskId = GetQueryParamValueByName(queryParams, L"TaskId"); + return make(taskId.c_str()); } // IInternalValueMarshalable diff --git a/dev/Common/Common.vcxitems b/dev/Common/Common.vcxitems index 6b554ac6f2..a500b1bc4d 100644 --- a/dev/Common/Common.vcxitems +++ b/dev/Common/Common.vcxitems @@ -28,6 +28,7 @@ + diff --git a/dev/Common/Common.vcxitems.filters b/dev/Common/Common.vcxitems.filters index cff6f33620..8d682d7c96 100644 --- a/dev/Common/Common.vcxitems.filters +++ b/dev/Common/Common.vcxitems.filters @@ -47,6 +47,9 @@ Header Files + + Header Files + diff --git a/dev/Common/UriHelpers.h b/dev/Common/UriHelpers.h new file mode 100644 index 0000000000..fb12da8341 --- /dev/null +++ b/dev/Common/UriHelpers.h @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include + +namespace winrt::Microsoft::Windows::AppLifecycle::implementation +{ + // Custom query parameter parser that can handle Unicode characters + inline std::map ParseUriQueryParameters(const winrt::Windows::Foundation::Uri& uri) + { + std::map queryParams; + + // Get the raw query string + std::wstring queryString = uri.Query().c_str(); + + // Remove the leading '?' if present + if (!queryString.empty() && queryString[0] == L'?') + { + queryString = queryString.substr(1); + } + + // Split the query string by '&' + size_t startPos = 0; + size_t endPos = queryString.find(L'&'); + + while (startPos < queryString.length()) + { + std::wstring keyValuePair; + + if (endPos == std::wstring::npos) + { + keyValuePair = queryString.substr(startPos); + startPos = queryString.length(); + } + else + { + keyValuePair = queryString.substr(startPos, endPos - startPos); + startPos = endPos + 1; + endPos = queryString.find(L'&', startPos); + } + + // Split the key-value pair by '=' + size_t separatorPos = keyValuePair.find(L'='); + if (separatorPos != std::wstring::npos) + { + std::wstring key = keyValuePair.substr(0, separatorPos); + std::wstring value = keyValuePair.substr(separatorPos + 1); + + // Unescape the value to handle Unicode characters + if (!value.empty()) + { + value = winrt::Windows::Foundation::Uri::UnescapeComponent(value).c_str(); + } + + queryParams[key] = value; + } + } + + return queryParams; + } + + // Helper function to get a value by name from the query parameters + inline std::wstring GetQueryParamValueByName(const std::map& queryParams, const std::wstring& name) + { + auto it = queryParams.find(name); + if (it != queryParams.end()) + { + return it->second; + } + return L""; + } +} \ No newline at end of file diff --git a/dev/PushNotifications/PushNotificationManager.cpp b/dev/PushNotifications/PushNotificationManager.cpp index 4cb15e2367..70d732bf9f 100644 --- a/dev/PushNotifications/PushNotificationManager.cpp +++ b/dev/PushNotifications/PushNotificationManager.cpp @@ -24,6 +24,7 @@ #include "PushNotificationReceivedEventArgs.h" #include #include +#include "../Common/UriHelpers.h" using namespace std::literals; using namespace Microsoft::Windows::AppNotifications::Helpers; @@ -195,16 +196,17 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation } else // The process was launched via ShellExecute and we need to parse the uri (Only unpackaged) { - for (auto const& pair : uri.QueryParsed()) + // Use custom query parameter parser to handle Unicode characters + auto queryParams = winrt::Microsoft::Windows::AppLifecycle::implementation::ParseUriQueryParameters(uri); + auto payload = winrt::Microsoft::Windows::AppLifecycle::implementation::GetQueryParamValueByName(queryParams, L"payload"); + + if (!payload.empty()) { - if (pair.Name() == L"payload") - { - // Convert escaped components to its normal content from the conversion done in the Long Running Process (see NotificationListener.cpp) - auto payloadAsWstring{ winrt::Uri::UnescapeComponent(pair.Value()) }; - auto backgroundTask{ winrt::make_self(payloadAsWstring.c_str()) }; - eventArgs = winrt::make(backgroundTask.as()); - m_backgroundTaskArgs = eventArgs; - } + // Convert escaped components to its normal content from the conversion done in the Long Running Process (see NotificationListener.cpp) + auto payloadAsWstring = winrt::Uri::UnescapeComponent(payload.c_str()); + auto backgroundTask = winrt::make_self(payloadAsWstring.c_str()); + eventArgs = winrt::make(backgroundTask.as()); + m_backgroundTaskArgs = eventArgs; } THROW_HR_IF_NULL_MSG(E_UNEXPECTED, eventArgs, "Could not serialize payload from command line Uri!"); From 4b15146f2ea9bc10b1ed495ea9e18f06a9605196 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 May 2025 03:42:45 +0000 Subject: [PATCH 6/7] Implement targeted solution for Unicode characters in file paths Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- dev/AppLifecycle/ExtensionContract.h | 38 ++------ dev/AppLifecycle/FileActivatedEventArgs.h | 89 +++++++++++++++++-- dev/AppLifecycle/LaunchActivatedEventArgs.h | 8 +- dev/AppLifecycle/ProtocolActivatedEventArgs.h | 8 +- dev/AppLifecycle/StartupActivatedEventArgs.h | 8 +- dev/Common/Common.vcxitems | 1 - dev/Common/Common.vcxitems.filters | 3 - dev/Common/UriHelpers.h | 76 ---------------- .../PushNotificationManager.cpp | 20 ++--- 9 files changed, 110 insertions(+), 141 deletions(-) delete mode 100644 dev/Common/UriHelpers.h diff --git a/dev/AppLifecycle/ExtensionContract.h b/dev/AppLifecycle/ExtensionContract.h index 8c44cbe258..c2f61d73dd 100644 --- a/dev/AppLifecycle/ExtensionContract.h +++ b/dev/AppLifecycle/ExtensionContract.h @@ -9,7 +9,6 @@ #include "StartupActivatedEventArgs.h" #include "PushNotificationManager.h" #include "AppNotificationManager.h" -#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -38,40 +37,21 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation inline std::tuple DecodeActivatedEventArgs(winrt::Windows::Foundation::Uri const& uri) { - // Use custom query parameter parser to handle Unicode characters - auto queryParams = ParseUriQueryParameters(uri); - - // Check for ContractId in the query parameters - auto contractIdIt = queryParams.find(c_contractIdKeyName); - if (contractIdIt != queryParams.end()) + for (auto const& pair : uri.QueryParsed()) { - auto contractId = contractIdIt->second; - - for (const auto& extension : c_extensionMap) + if (CompareStringOrdinal(pair.Name().c_str(), -1, c_contractIdKeyName, -1, TRUE) == CSTR_EQUAL) { - if (CompareStringOrdinal(contractId.c_str(), -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) + auto contractId = pair.Value().c_str(); + for (const auto& extension : c_extensionMap) { - return { extension.kind, extension.factory(uri) }; + if (CompareStringOrdinal(contractId, -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) + { + return { extension.kind, extension.factory(uri) }; + } } } } - - // If we have a File parameter in the query, this is likely a file activation - // This is a fallback for when the ContractId parsing might fail - auto fileValue = GetQueryParamValueByName(queryParams, L"File"); - auto verbValue = GetQueryParamValueByName(queryParams, L"Verb"); - - if (!fileValue.empty() && !verbValue.empty()) - { - for (const auto& extension : c_extensionMap) - { - if (CompareStringOrdinal(extension.contractId, -1, c_fileContractId, -1, TRUE) == CSTR_EQUAL) - { - return { extension.kind, extension.factory(uri) }; - } - } - } - + return { ExtendedActivationKind::Protocol, nullptr }; } } diff --git a/dev/AppLifecycle/FileActivatedEventArgs.h b/dev/AppLifecycle/FileActivatedEventArgs.h index 18dc75bc50..ae9f407ef8 100644 --- a/dev/AppLifecycle/FileActivatedEventArgs.h +++ b/dev/AppLifecycle/FileActivatedEventArgs.h @@ -3,7 +3,6 @@ #pragma once #include "ActivatedEventArgsBase.h" -#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -48,12 +47,90 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - // Use custom query parameter parser to handle Unicode characters - auto queryParams = ParseUriQueryParameters(uri); - auto verb = GetQueryParamValueByName(queryParams, L"Verb"); - auto file = GetQueryParamValueByName(queryParams, L"File"); + winrt::hstring verb; + winrt::hstring file; - return make(verb.c_str(), file.c_str()); + // Try to get parameters using standard QueryParsed + try + { + auto query = uri.QueryParsed(); + verb = query.GetFirstValueByName(L"Verb"); + } + catch (...) + { + // Fall back to manual extraction if QueryParsed fails + verb = ExtractQueryParameterValue(uri.Query(), L"Verb"); + } + + // For File parameter, always use direct extraction since: + // 1. It can contain Unicode characters which QueryParsed() might not handle correctly + // 2. It can contain & characters in the filename + // 3. It's always the last parameter in the URI (by convention in the Serialize method) + file = ExtractFileParameterValue(uri.Query()); + + if (!verb.empty() && !file.empty()) + { + return make(verb, file); + } + + throw winrt::hresult_invalid_argument(L"Missing required parameters"); + } + + // Helper method to extract the File parameter value, assuming File is the last parameter + static winrt::hstring ExtractFileParameterValue(winrt::hstring const& query) + { + std::wstring queryStr = query.c_str(); + const std::wstring fileParamPrefix = L"File="; + + // Find File parameter + auto filePos = queryStr.find(fileParamPrefix); + if (filePos != std::wstring::npos) + { + // Extract everything after "File=" to the end + auto fileValue = queryStr.substr(filePos + fileParamPrefix.length()); + + // Unescape to handle Unicode characters + if (!fileValue.empty()) + { + return winrt::Windows::Foundation::Uri::UnescapeComponent(fileValue); + } + } + + return L""; + } + + // Helper method to extract a generic query parameter value + static winrt::hstring ExtractQueryParameterValue(winrt::hstring const& query, std::wstring const& paramName) + { + std::wstring queryStr = query.c_str(); + std::wstring paramPrefix = paramName + L"="; + + // Find parameter + auto paramPos = queryStr.find(paramPrefix); + if (paramPos != std::wstring::npos) + { + // Extract from start position to next & or end + auto startPos = paramPos + paramPrefix.length(); + auto endPos = queryStr.find(L'&', startPos); + + std::wstring paramValue; + if (endPos != std::wstring::npos) + { + paramValue = queryStr.substr(startPos, endPos - startPos); + } + else + { + paramValue = queryStr.substr(startPos); + } + + // Unescape the value + if (!paramValue.empty()) + { + return winrt::Windows::Foundation::Uri::UnescapeComponent(paramValue); + } + } + + return L""; } // IInternalValueMarshalable diff --git a/dev/AppLifecycle/LaunchActivatedEventArgs.h b/dev/AppLifecycle/LaunchActivatedEventArgs.h index d861a3aa5d..9ad86b0fcf 100644 --- a/dev/AppLifecycle/LaunchActivatedEventArgs.h +++ b/dev/AppLifecycle/LaunchActivatedEventArgs.h @@ -4,7 +4,6 @@ #include "ActivatedEventArgsBase.h" #include "ValueMarshaling.h" -#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -23,10 +22,9 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - // Use custom query parameter parser to handle Unicode characters - auto queryParams = ParseUriQueryParameters(uri); - auto args = GetQueryParamValueByName(queryParams, L"Arguments"); - return make(args.c_str()); + auto query = uri.QueryParsed(); + auto args = query.GetFirstValueByName(L"Arguments"); + return make(args); } // IInternalValueMarshalable diff --git a/dev/AppLifecycle/ProtocolActivatedEventArgs.h b/dev/AppLifecycle/ProtocolActivatedEventArgs.h index f0df81df94..7caef91d70 100644 --- a/dev/AppLifecycle/ProtocolActivatedEventArgs.h +++ b/dev/AppLifecycle/ProtocolActivatedEventArgs.h @@ -4,7 +4,6 @@ #include #include "ActivatedEventArgsBase.h" -#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -24,10 +23,9 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - // Use custom query parameter parser to handle Unicode characters - auto queryParams = ParseUriQueryParameters(uri); - auto args = GetQueryParamValueByName(queryParams, L"Uri"); - return make(args.c_str()); + auto query = uri.QueryParsed(); + auto args = query.GetFirstValueByName(L"Uri"); + return make(args); } // IInternalValueMarshalable diff --git a/dev/AppLifecycle/StartupActivatedEventArgs.h b/dev/AppLifecycle/StartupActivatedEventArgs.h index 95258aaa16..1d33a3909c 100644 --- a/dev/AppLifecycle/StartupActivatedEventArgs.h +++ b/dev/AppLifecycle/StartupActivatedEventArgs.h @@ -4,7 +4,6 @@ #include #include "ActivatedEventArgsBase.h" -#include "../Common/UriHelpers.h" namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -23,10 +22,9 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri) { - // Use custom query parameter parser to handle Unicode characters - auto queryParams = ParseUriQueryParameters(uri); - auto taskId = GetQueryParamValueByName(queryParams, L"TaskId"); - return make(taskId.c_str()); + auto query = uri.QueryParsed(); + auto taskId = query.GetFirstValueByName(L"TaskId"); + return make(taskId); } // IInternalValueMarshalable diff --git a/dev/Common/Common.vcxitems b/dev/Common/Common.vcxitems index a500b1bc4d..6b554ac6f2 100644 --- a/dev/Common/Common.vcxitems +++ b/dev/Common/Common.vcxitems @@ -28,7 +28,6 @@ - diff --git a/dev/Common/Common.vcxitems.filters b/dev/Common/Common.vcxitems.filters index 8d682d7c96..cff6f33620 100644 --- a/dev/Common/Common.vcxitems.filters +++ b/dev/Common/Common.vcxitems.filters @@ -47,9 +47,6 @@ Header Files - - Header Files - diff --git a/dev/Common/UriHelpers.h b/dev/Common/UriHelpers.h deleted file mode 100644 index fb12da8341..0000000000 --- a/dev/Common/UriHelpers.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License. -#pragma once - -#include -#include -#include -#include - -namespace winrt::Microsoft::Windows::AppLifecycle::implementation -{ - // Custom query parameter parser that can handle Unicode characters - inline std::map ParseUriQueryParameters(const winrt::Windows::Foundation::Uri& uri) - { - std::map queryParams; - - // Get the raw query string - std::wstring queryString = uri.Query().c_str(); - - // Remove the leading '?' if present - if (!queryString.empty() && queryString[0] == L'?') - { - queryString = queryString.substr(1); - } - - // Split the query string by '&' - size_t startPos = 0; - size_t endPos = queryString.find(L'&'); - - while (startPos < queryString.length()) - { - std::wstring keyValuePair; - - if (endPos == std::wstring::npos) - { - keyValuePair = queryString.substr(startPos); - startPos = queryString.length(); - } - else - { - keyValuePair = queryString.substr(startPos, endPos - startPos); - startPos = endPos + 1; - endPos = queryString.find(L'&', startPos); - } - - // Split the key-value pair by '=' - size_t separatorPos = keyValuePair.find(L'='); - if (separatorPos != std::wstring::npos) - { - std::wstring key = keyValuePair.substr(0, separatorPos); - std::wstring value = keyValuePair.substr(separatorPos + 1); - - // Unescape the value to handle Unicode characters - if (!value.empty()) - { - value = winrt::Windows::Foundation::Uri::UnescapeComponent(value).c_str(); - } - - queryParams[key] = value; - } - } - - return queryParams; - } - - // Helper function to get a value by name from the query parameters - inline std::wstring GetQueryParamValueByName(const std::map& queryParams, const std::wstring& name) - { - auto it = queryParams.find(name); - if (it != queryParams.end()) - { - return it->second; - } - return L""; - } -} \ No newline at end of file diff --git a/dev/PushNotifications/PushNotificationManager.cpp b/dev/PushNotifications/PushNotificationManager.cpp index 70d732bf9f..4cb15e2367 100644 --- a/dev/PushNotifications/PushNotificationManager.cpp +++ b/dev/PushNotifications/PushNotificationManager.cpp @@ -24,7 +24,6 @@ #include "PushNotificationReceivedEventArgs.h" #include #include -#include "../Common/UriHelpers.h" using namespace std::literals; using namespace Microsoft::Windows::AppNotifications::Helpers; @@ -196,17 +195,16 @@ namespace winrt::Microsoft::Windows::PushNotifications::implementation } else // The process was launched via ShellExecute and we need to parse the uri (Only unpackaged) { - // Use custom query parameter parser to handle Unicode characters - auto queryParams = winrt::Microsoft::Windows::AppLifecycle::implementation::ParseUriQueryParameters(uri); - auto payload = winrt::Microsoft::Windows::AppLifecycle::implementation::GetQueryParamValueByName(queryParams, L"payload"); - - if (!payload.empty()) + for (auto const& pair : uri.QueryParsed()) { - // Convert escaped components to its normal content from the conversion done in the Long Running Process (see NotificationListener.cpp) - auto payloadAsWstring = winrt::Uri::UnescapeComponent(payload.c_str()); - auto backgroundTask = winrt::make_self(payloadAsWstring.c_str()); - eventArgs = winrt::make(backgroundTask.as()); - m_backgroundTaskArgs = eventArgs; + if (pair.Name() == L"payload") + { + // Convert escaped components to its normal content from the conversion done in the Long Running Process (see NotificationListener.cpp) + auto payloadAsWstring{ winrt::Uri::UnescapeComponent(pair.Value()) }; + auto backgroundTask{ winrt::make_self(payloadAsWstring.c_str()) }; + eventArgs = winrt::make(backgroundTask.as()); + m_backgroundTaskArgs = eventArgs; + } } THROW_HR_IF_NULL_MSG(E_UNEXPECTED, eventArgs, "Could not serialize payload from command line Uri!"); From 8d1400944212a89421cdcbe47385c0980d26df9e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 May 2025 09:06:51 +0000 Subject: [PATCH 7/7] Fix QueryParsed usage in ExtensionContract.h for Unicode characters Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- BuildAll.cmd | 0 BuildAll.ps1 | 0 dev/AppLifecycle/ExtensionContract.h | 41 +++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) mode change 100644 => 100755 BuildAll.cmd mode change 100644 => 100755 BuildAll.ps1 diff --git a/BuildAll.cmd b/BuildAll.cmd old mode 100644 new mode 100755 diff --git a/BuildAll.ps1 b/BuildAll.ps1 old mode 100644 new mode 100755 diff --git a/dev/AppLifecycle/ExtensionContract.h b/dev/AppLifecycle/ExtensionContract.h index c2f61d73dd..5fb11111ae 100644 --- a/dev/AppLifecycle/ExtensionContract.h +++ b/dev/AppLifecycle/ExtensionContract.h @@ -37,14 +37,47 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation inline std::tuple DecodeActivatedEventArgs(winrt::Windows::Foundation::Uri const& uri) { - for (auto const& pair : uri.QueryParsed()) + // First try to use QueryParsed() which works for regular characters + try { - if (CompareStringOrdinal(pair.Name().c_str(), -1, c_contractIdKeyName, -1, TRUE) == CSTR_EQUAL) + for (auto const& pair : uri.QueryParsed()) + { + if (CompareStringOrdinal(pair.Name().c_str(), -1, c_contractIdKeyName, -1, TRUE) == CSTR_EQUAL) + { + auto contractId = pair.Value().c_str(); + for (const auto& extension : c_extensionMap) + { + if (CompareStringOrdinal(contractId, -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) + { + return { extension.kind, extension.factory(uri) }; + } + } + } + } + } + catch (...) + { + // If QueryParsed() fails (likely due to Unicode characters), fall back to manual extraction + auto contractId = FileActivatedEventArgs::ExtractQueryParameterValue(uri.Query(), c_contractIdKeyName); + if (!contractId.empty()) + { + for (const auto& extension : c_extensionMap) + { + if (CompareStringOrdinal(contractId.c_str(), -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) + { + return { extension.kind, extension.factory(uri) }; + } + } + } + + // Check for File activation as a fallback (since it's a common case with Unicode characters) + auto fileParam = FileActivatedEventArgs::ExtractFileParameterValue(uri.Query()); + auto verbParam = FileActivatedEventArgs::ExtractQueryParameterValue(uri.Query(), L"Verb"); + if (!fileParam.empty() && !verbParam.empty()) { - auto contractId = pair.Value().c_str(); for (const auto& extension : c_extensionMap) { - if (CompareStringOrdinal(contractId, -1, extension.contractId, -1, TRUE) == CSTR_EQUAL) + if (CompareStringOrdinal(extension.contractId, -1, c_fileContractId, -1, TRUE) == CSTR_EQUAL) { return { extension.kind, extension.factory(uri) }; }