Skip to content

Commit e1e6e84

Browse files
knoppmauricioluz
authored andcommitted
[path_provider] Support unicode encoded version info values (flutter#4986)
1 parent a7aa783 commit e1e6e84

File tree

4 files changed

+92
-16
lines changed

4 files changed

+92
-16
lines changed

packages/path_provider/path_provider_windows/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 2.0.7
22

3+
* Added support for unicode encoded VERSIONINFO
34
* Minor fixes for new analysis options.
45

56
## 2.0.6

packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart

+37-9
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,43 @@ import 'package:win32/win32.dart';
1313

1414
import 'folders.dart';
1515

16+
/// Constant for en-US language used in VersionInfo keys.
17+
@visibleForTesting
18+
const String languageEn = '0409';
19+
20+
/// Constant for CP1252 encoding used in VersionInfo keys
21+
@visibleForTesting
22+
const String encodingCP1252 = '04e4';
23+
24+
/// Constant for Unicode encoding used in VersionInfo keys
25+
@visibleForTesting
26+
const String encodingUnicode = '04b0';
27+
1628
/// Wraps the Win32 VerQueryValue API call.
1729
///
1830
/// This class exists to allow injecting alternate metadata in tests without
1931
/// building multiple custom test binaries.
2032
@visibleForTesting
2133
class VersionInfoQuerier {
22-
/// Returns the value for [key] in [versionInfo]s English strings section, or
23-
/// null if there is no such entry, or if versionInfo is null.
24-
String? getStringValue(Pointer<Uint8>? versionInfo, String key) {
34+
/// Returns the value for [key] in [versionInfo]s in section with given
35+
/// language and encoding, or null if there is no such entry,
36+
/// or if versionInfo is null.
37+
///
38+
/// See https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
39+
/// for list of possible language and encoding values.
40+
String? getStringValue(
41+
Pointer<Uint8>? versionInfo,
42+
String key, {
43+
required String language,
44+
required String encoding,
45+
}) {
46+
assert(language.isNotEmpty);
47+
assert(encoding.isNotEmpty);
2548
if (versionInfo == null) {
2649
return null;
2750
}
28-
const String kEnUsLanguageCode = '040904e4';
2951
final Pointer<Utf16> keyPath =
30-
TEXT('\\StringFileInfo\\$kEnUsLanguageCode\\$key');
52+
TEXT('\\StringFileInfo\\$language$encoding\\$key');
3153
final Pointer<Uint32> length = calloc<Uint32>();
3254
final Pointer<Pointer<Utf16>> valueAddress = calloc<Pointer<Utf16>>();
3355
try {
@@ -150,6 +172,12 @@ class PathProviderWindows extends PathProviderPlatform {
150172
}
151173
}
152174

175+
String? _getStringValue(Pointer<Uint8>? infoBuffer, String key) =>
176+
versionInfoQuerier.getStringValue(infoBuffer, key,
177+
language: languageEn, encoding: encodingCP1252) ??
178+
versionInfoQuerier.getStringValue(infoBuffer, key,
179+
language: languageEn, encoding: encodingUnicode);
180+
153181
/// Returns the relative path string to append to the root directory returned
154182
/// by Win32 APIs for application storage (such as RoamingAppDir) to get a
155183
/// directory that is unique to the application.
@@ -187,10 +215,10 @@ class PathProviderWindows extends PathProviderPlatform {
187215
infoBuffer = null;
188216
}
189217
}
190-
companyName = _sanitizedDirectoryName(
191-
versionInfoQuerier.getStringValue(infoBuffer, 'CompanyName'));
192-
productName = _sanitizedDirectoryName(
193-
versionInfoQuerier.getStringValue(infoBuffer, 'ProductName'));
218+
companyName =
219+
_sanitizedDirectoryName(_getStringValue(infoBuffer, 'CompanyName'));
220+
productName =
221+
_sanitizedDirectoryName(_getStringValue(infoBuffer, 'ProductName'));
194222

195223
// If there was no product name, use the executable name.
196224
productName ??=

packages/path_provider/path_provider_windows/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: path_provider_windows
22
description: Windows implementation of the path_provider plugin
33
repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_windows
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
5-
version: 2.0.6
5+
version: 2.0.7
66

77
environment:
88
sdk: ">=2.12.0 <3.0.0"

packages/path_provider/path_provider_windows/test/path_provider_windows_test.dart

+52-5
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,33 @@ import 'dart:io';
77
import 'package:flutter_test/flutter_test.dart';
88
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
99
import 'package:path_provider_windows/path_provider_windows.dart';
10+
import 'package:path_provider_windows/src/path_provider_windows_real.dart'
11+
show languageEn, encodingCP1252, encodingUnicode;
1012

1113
// A fake VersionInfoQuerier that just returns preset responses.
1214
class FakeVersionInfoQuerier implements VersionInfoQuerier {
13-
FakeVersionInfoQuerier(this.responses);
15+
FakeVersionInfoQuerier(
16+
this.responses, {
17+
this.language = languageEn,
18+
this.encoding = encodingUnicode,
19+
});
1420

21+
final String language;
22+
final String encoding;
1523
final Map<String, String> responses;
1624

17-
String? getStringValue(Pointer<Uint8>? versionInfo, String key) =>
18-
responses[key];
25+
String? getStringValue(
26+
Pointer<Uint8>? versionInfo,
27+
String key, {
28+
required String language,
29+
required String encoding,
30+
}) {
31+
if (language == this.language && encoding == this.encoding) {
32+
return responses[key];
33+
} else {
34+
return null;
35+
}
36+
}
1937
}
2038

2139
void main() {
@@ -40,12 +58,26 @@ void main() {
4058
expect(path, endsWith(r'flutter_tester'));
4159
}, skip: !Platform.isWindows);
4260

43-
test('getApplicationSupportPath with full version info', () async {
61+
test('getApplicationSupportPath with full version info in CP1252', () async {
4462
final PathProviderWindows pathProvider = PathProviderWindows();
4563
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
4664
'CompanyName': 'A Company',
4765
'ProductName': 'Amazing App',
48-
});
66+
}, language: languageEn, encoding: encodingCP1252);
67+
final String? path = await pathProvider.getApplicationSupportPath();
68+
expect(path, isNotNull);
69+
if (path != null) {
70+
expect(path, endsWith(r'AppData\Roaming\A Company\Amazing App'));
71+
expect(Directory(path).existsSync(), isTrue);
72+
}
73+
}, skip: !Platform.isWindows);
74+
75+
test('getApplicationSupportPath with full version info in Unicode', () async {
76+
final PathProviderWindows pathProvider = PathProviderWindows();
77+
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
78+
'CompanyName': 'A Company',
79+
'ProductName': 'Amazing App',
80+
}, language: languageEn, encoding: encodingUnicode);
4981
final String? path = await pathProvider.getApplicationSupportPath();
5082
expect(path, isNotNull);
5183
if (path != null) {
@@ -54,6 +86,21 @@ void main() {
5486
}
5587
}, skip: !Platform.isWindows);
5688

89+
test(
90+
'getApplicationSupportPath with full version info in Unsupported Encoding',
91+
() async {
92+
final PathProviderWindows pathProvider = PathProviderWindows();
93+
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
94+
'CompanyName': 'A Company',
95+
'ProductName': 'Amazing App',
96+
}, language: '0000', encoding: '0000');
97+
final String? path = await pathProvider.getApplicationSupportPath();
98+
expect(path, contains(r'C:\'));
99+
expect(path, contains(r'AppData'));
100+
// The last path component should be the executable name.
101+
expect(path, endsWith(r'flutter_tester'));
102+
}, skip: !Platform.isWindows);
103+
57104
test('getApplicationSupportPath with missing company', () async {
58105
final PathProviderWindows pathProvider = PathProviderWindows();
59106
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{

0 commit comments

Comments
 (0)