Skip to content

Commit c23f4be

Browse files
authored
[url_launcher] Decode file URLs before passing it to ShellExecuteW (#7774)
- ShellExecuteW does not handle file: urls that contain %-encoded UTF-8 strings correctly. %-encoded ASCII strings are handled correctly, as are file "urls" that contain Unicode strings in its path component. - This change perform URL decode on file: urls before passing to ShellExecuteW. Fixes flutter/flutter#156790
1 parent e19503e commit c23f4be

File tree

5 files changed

+44
-5
lines changed

5 files changed

+44
-5
lines changed

packages/url_launcher/url_launcher_windows/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## NEXT
1+
## 3.1.3
22

33
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
4+
* Fixes handling of `file:` URLs that contain UTF-8 encoded paths.
45

56
## 3.1.2
67

packages/url_launcher/url_launcher_windows/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: url_launcher_windows
22
description: Windows implementation of the url_launcher plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
5-
version: 3.1.2
5+
version: 3.1.3
66

77
environment:
88
sdk: ^3.3.0

packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
2525
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
2626
target_include_directories(${PLUGIN_NAME} INTERFACE
2727
"${CMAKE_CURRENT_SOURCE_DIR}/include")
28-
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
28+
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin shlwapi.lib)
2929

3030
# List of absolute paths to libraries that should be bundled with the plugin
3131
set(file_chooser_bundled_libraries
@@ -62,7 +62,7 @@ add_executable(${TEST_RUNNER}
6262
)
6363
apply_standard_settings(${TEST_RUNNER})
6464
target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
65-
target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin)
65+
target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin shlwapi.lib)
6666
target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock)
6767
# flutter_wrapper_plugin has link dependencies on the Flutter DLL.
6868
add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD

packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ namespace {
2222

2323
using flutter::EncodableMap;
2424
using flutter::EncodableValue;
25+
using ::testing::_;
2526
using ::testing::DoAll;
2627
using ::testing::Pointee;
2728
using ::testing::Return;
2829
using ::testing::SetArgPointee;
30+
using ::testing::StrEq;
2931

3032
class MockSystemApis : public SystemApis {
3133
public:
@@ -135,5 +137,28 @@ TEST(UrlLauncherPlugin, LaunchReportsError) {
135137
EXPECT_TRUE(result.has_error());
136138
}
137139

140+
TEST(UrlLauncherPlugin, LaunchUTF8EncodedFileURLSuccess) {
141+
std::unique_ptr<MockSystemApis> system = std::make_unique<MockSystemApis>();
142+
143+
// Return a success value (>32) from launching.
144+
EXPECT_CALL(
145+
*system,
146+
ShellExecuteW(
147+
_, StrEq(L"open"),
148+
// 家の管理/スキャナ"),
149+
StrEq(
150+
L"file:///G:/\x5bb6\x306e\x7ba1\x7406/\x30b9\x30ad\x30e3\x30ca"),
151+
_, _, _))
152+
.WillOnce(Return(reinterpret_cast<HINSTANCE>(33)));
153+
154+
UrlLauncherPlugin plugin(std::move(system));
155+
ErrorOr<bool> result = plugin.LaunchUrl(
156+
"file:///G:/%E5%AE%B6%E3%81%AE%E7%AE%A1%E7%90%86/"
157+
"%E3%82%B9%E3%82%AD%E3%83%A3%E3%83%8A");
158+
159+
ASSERT_FALSE(result.has_error());
160+
EXPECT_TRUE(result.value());
161+
}
162+
138163
} // namespace test
139164
} // namespace url_launcher_windows

packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <flutter/method_channel.h>
77
#include <flutter/plugin_registrar_windows.h>
88
#include <flutter/standard_method_codec.h>
9+
#include <shlwapi.h>
910
#include <windows.h>
1011

1112
#include <memory>
@@ -98,7 +99,19 @@ ErrorOr<bool> UrlLauncherPlugin::CanLaunchUrl(const std::string& url) {
9899
}
99100

100101
ErrorOr<bool> UrlLauncherPlugin::LaunchUrl(const std::string& url) {
101-
std::wstring url_wide = Utf16FromUtf8(url);
102+
std::wstring url_wide;
103+
if (url.find("file:") == 0) {
104+
// ShellExecuteW does not process %-encoded UTF8 strings in file URLs.
105+
DWORD unescaped_len = 0;
106+
std::string unescaped_url = url;
107+
if (FAILED(::UrlUnescapeA(unescaped_url.data(), /*pszUnescaped=*/nullptr,
108+
&unescaped_len, URL_UNESCAPE_INPLACE))) {
109+
return FlutterError("open_error", "Failed to unescape file URL");
110+
}
111+
url_wide = Utf16FromUtf8(unescaped_url);
112+
} else {
113+
url_wide = Utf16FromUtf8(url);
114+
}
102115

103116
int status = static_cast<int>(reinterpret_cast<INT_PTR>(
104117
system_apis_->ShellExecuteW(nullptr, TEXT("open"), url_wide.c_str(),

0 commit comments

Comments
 (0)