Skip to content

Commit e8d5edf

Browse files
authored
[Issue #3871] File activation for unpackaged App would fail when file path contains unicode characters or special characters (#5175)
* Add special logic to use string operations for file uri parsing * minor refactor to increase readability and simplify logic * Add test * refine comments * Add description on error
1 parent c35ff7b commit e8d5edf

3 files changed

Lines changed: 90 additions & 6 deletions

File tree

dev/AppLifecycle/ExtensionContract.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation and Contributors.
1+
// Copyright (c) Microsoft Corporation and Contributors.
22
// Licensed under the MIT License.
33
#pragma once
44

@@ -52,6 +52,14 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation
5252
}
5353
}
5454

55+
// QueryParsed() function would return empty when it's a file contract with unicode characters in file path
56+
// Thus following additional check for file contract is needed
57+
auto fileContractUri = GenerateEncodedLaunchUri(L"App", c_fileContractId);
58+
if (CompareStringOrdinal(uri.AbsoluteUri().c_str(), fileContractUri.length(), fileContractUri.c_str(), -1, TRUE) == CSTR_EQUAL)
59+
{
60+
return { ExtendedActivationKind::File, FileActivatedEventArgs::Deserialize(uri) };
61+
}
62+
5563
return { ExtendedActivationKind::Protocol, nullptr };
5664
}
5765
}

dev/AppLifecycle/FileActivatedEventArgs.h

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation and Contributors.
1+
// Copyright (c) Microsoft Corporation and Contributors.
22
// Licensed under the MIT License.
33
#pragma once
44

@@ -47,9 +47,46 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation
4747

4848
static winrt::Windows::Foundation::IInspectable Deserialize(winrt::Windows::Foundation::Uri const& uri)
4949
{
50-
auto query = uri.QueryParsed();
51-
auto verb = query.GetFirstValueByName(L"Verb");
52-
auto file = query.GetFirstValueByName(L"File");
50+
auto parsedQuery = uri.QueryParsed();
51+
52+
// By design parsed query should have a size of 3 (ContractId, Verb, File), in which case result from QueryParsed() can be used directly.
53+
// 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.
54+
if (parsedQuery.Size() == 3)
55+
{
56+
auto verb = parsedQuery.GetFirstValueByName(L"Verb");
57+
auto file = parsedQuery.GetFirstValueByName(L"File");
58+
return make<FileActivatedEventArgs>(verb, file);
59+
}
60+
61+
const std::wstring query = uri.Query().c_str();
62+
auto queryLength = query.length();
63+
64+
auto verbBegin = query.find(L"&Verb=");
65+
if (verbBegin == std::wstring::npos)
66+
{
67+
throw winrt::hresult_invalid_argument(L"Query of encoded file protocol should contain 'Verb'");
68+
}
69+
verbBegin += 6; // Length of "&Verb="
70+
71+
auto verbEnd = query.find(L"&", verbBegin);
72+
if (verbEnd == std::wstring::npos)
73+
{
74+
verbEnd = queryLength;
75+
}
76+
77+
auto fileBegin = query.find(L"&File=");
78+
if (fileBegin == std::wstring::npos)
79+
{
80+
throw winrt::hresult_invalid_argument(L"Query of encoded file protocol should contain 'File'");
81+
}
82+
fileBegin += 6; // Length of "&File="
83+
84+
// File path may contain '&' character, so fileEnd can only be assumed to be the end of the query or start of Verb
85+
auto fileEnd = verbBegin > fileBegin ? verbBegin : queryLength;
86+
87+
auto verb = winrt::to_hstring(query.substr(verbBegin, verbEnd - verbBegin).c_str());
88+
auto file = winrt::to_hstring(query.substr(fileBegin, fileEnd - fileBegin).c_str());
89+
5390
return make<FileActivatedEventArgs>(verb, file);
5491
}
5592

test/AppLifecycle/FunctionalTests.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation and Contributors.
1+
// Copyright (c) Microsoft Corporation and Contributors.
22
// Licensed under the MIT License.
33

44
#include "pch.h"
@@ -29,6 +29,7 @@ namespace Test::AppLifecycle
2929
std::wstring m_testName;
3030

3131
const std::wstring c_testDataFileName = L"testfile" + c_testFileExtension;
32+
const std::wstring c_testDataFileName_Unicode = L"你好&世界" + c_testFileExtension;
3233
const std::wstring c_testDataFileName_Packaged = L"testfile" + c_testFileExtension_Packaged;
3334
const std::wstring c_testPackageFile = g_deploymentDir + L"AppLifecycleTestPackage.msix";
3435
const std::wstring c_testVCLibsPackageFile = g_deploymentDir + L"VCLibs.appx";
@@ -52,6 +53,7 @@ namespace Test::AppLifecycle
5253

5354
// Write out some test content.
5455
WriteContentFile(c_testDataFileName);
56+
WriteContentFile(c_testDataFileName_Unicode);
5557
WriteContentFile(c_testDataFileName_Packaged);
5658

5759
return true;
@@ -63,6 +65,7 @@ namespace Test::AppLifecycle
6365
try
6466
{
6567
DeleteContentFile(c_testDataFileName_Packaged);
68+
DeleteContentFile(c_testDataFileName_Unicode);
6669
DeleteContentFile(c_testDataFileName);
6770
UninstallPackage(c_testPackageFullName);
6871
}
@@ -128,6 +131,42 @@ namespace Test::AppLifecycle
128131
WaitForEvent(event, m_failed);
129132
}
130133

134+
TEST_METHOD(GetActivatedEventArgsForUnicodeNamedFile_Win32)
135+
{
136+
// Create a named event for communicating with test app.
137+
auto event = CreateTestEvent(c_testFilePhaseEventName);
138+
139+
// Cleanup any leftover data from previous runs i.e. ensure we running with a clean slate
140+
try
141+
{
142+
Execute(L"AppLifecycleTestApp.exe", L"/UnregisterFile", g_deploymentDir);
143+
WaitForEvent(event, m_failed);
144+
}
145+
catch (...)
146+
{
147+
//TODO:Unregister should not fail if ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND
148+
}
149+
150+
// Launch the test app to register for protocol launches.
151+
Execute(L"AppLifecycleTestApp.exe", L"/RegisterFile", g_deploymentDir);
152+
153+
// Wait for the register event.
154+
WaitForEvent(event, m_failed);
155+
156+
// Launch the file and wait for the event to fire.
157+
auto file = OpenDocFile(c_testDataFileName_Unicode);
158+
auto launchResult = Launcher::LaunchFileAsync(file).get();
159+
VERIFY_IS_TRUE(launchResult);
160+
161+
// Wait for the file activation.
162+
WaitForEvent(event, m_failed);
163+
164+
Execute(L"AppLifecycleTestApp.exe", L"/UnregisterFile", g_deploymentDir);
165+
166+
// Wait for the unregister event.
167+
WaitForEvent(event, m_failed);
168+
}
169+
131170
TEST_METHOD(GetActivatedEventArgsForFile_PackagedWin32)
132171
{
133172
// Create a named event for communicating with test app.

0 commit comments

Comments
 (0)